/*-------------------------------------------------------------------------
 *
 * execMain.c
 *	  顶级执行器接口例程
 *
 * 接口例程
 *	ExecutorStart()
 *	ExecutorRun()
 *	ExecutorFinish()
 *	ExecutorEnd()
 *
 *	这四个过程是执行器的外部接口。在每种情况下，查询描述符都是必需的参数。
 *
 *	ExecutorStart必须在执行任何查询计划的开始时调用，ExecutorEnd必须在执行计划结束时始终调用（除非因错误而中止）。
 *
 *	ExecutorRun接受方向和计数参数，这些参数指定计划是向前、向后执行，以及要执行多少个元组。在某些情况下，可以多次调用ExecutorRun以处理所有的计划元组。也可以在执行完整个计划之前提前停止（但仅限于SELECT）。
 *
 *	ExecutorFinish必须在最后一个ExecutorRun调用后以及ExecutorEnd之前调用。在EXPLAIN的情况下可以省略此调用，EXPLAIN也应省略ExecutorRun。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/execMain.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
#include "executor/nodeSubplan.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "parser/parsetree.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/backend_status.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/partcache.h"
#include "utils/rls.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"


/* 插件在 ExecutorStart/Run/Finish/End 中获取控制的钩子 */
ExecutorStart_hook_type ExecutorStart_hook = NULL;
ExecutorRun_hook_type ExecutorRun_hook = NULL;
ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
ExecutorEnd_hook_type ExecutorEnd_hook = NULL;

/* 插件在 ExecCheckRTPerms() 中获取控制的钩子 */
ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;

/* 仅在此模块内使用的本地例程的声明 */
static void fc_InitPlan(QueryDesc *fc_queryDesc, int fc_eflags);
static void fc_CheckValidRowMarkRel(Relation fc_rel, RowMarkType fc_markType);
static void fc_ExecPostprocessPlan(EState *fc_estate);
static void fc_ExecEndPlan(PlanState *fc_planstate, EState *fc_estate);
static void fc_ExecutePlan(QueryDesc *fc_queryDesc,
						CmdType fc_operation,
						bool fc_sendTuples,
						uint64 fc_numberTuples,
						ScanDirection fc_direction,
						DestReceiver *fc_dest);
static bool fc_ExecCheckRTEPerms(RangeTblEntry *fc_rte);
static bool fc_ExecCheckRTEPermsModified(Oid fc_relOid, Oid fc_userid,
									  Bitmapset *fc_modifiedCols,
									  AclMode fc_requiredPerms);
static void fc_ExecCheckXactReadOnly(PlannedStmt *fc_plannedstmt);
static char *fc_ExecBuildSlotValueDescription(Oid fc_reloid,
										   TupleTableSlot *fc_slot,
										   TupleDesc fc_tupdesc,
										   Bitmapset *fc_modifiedCols,
										   int fc_maxfieldlen);
static void fc_EvalPlanQualStart(EPQState *fc_epqstate, Plan *fc_planTree);

/* 本地声明的结束 */


/* ----------------------------------------------------------------
 *		ExecutorStart
 *
 *		此例程必须在任何查询计划的任何执行开始时调用
 *
 * 接受一个先前由 CreateQueryDesc 创建的 QueryDesc（仅因某些
 * 地方使用 QueryDescs 作为实用命令而分开）。QueryDesc 的
 * tupDesc 字段被填充以描述将返回的元组，内部字段
 * (estate 和 planstate) 被设置。
 *
 * eflags 包含如 executor.h 中所述的标志位。
 *
 * 注意：调用时的 CurrentMemoryContext 将成为该 Executor
 * 调用使用的每个查询上下文的父上下文。
 *
 * 我们提供了一个函数钩子变量，让可加载插件在调用
 * ExecutorStart 时获取控制。这样的插件通常会调用
 * standard_ExecutorStart()。
 *
 * ----------------------------------------------------------------
 */
void ExecutorStart(QueryDesc *fc_queryDesc, int fc_eflags)
{
	/*
	 * 在某些情况下（例如 EXECUTE 语句或带有
	 * 扩展查询协议的执行消息）不会报告 query_id，所以现在进行报告。
	 *
	 * 请注意，报告 query_id 多次是无害的，因为如果
	 * 顶层 query_id 已被报告，则调用将被忽略。
	 */
	pgstat_report_query_id(fc_queryDesc->plannedstmt->queryId, false);

	if (ExecutorStart_hook)
		(*ExecutorStart_hook) (fc_queryDesc, fc_eflags);
	else
		standard_ExecutorStart(fc_queryDesc, fc_eflags);
}

void standard_ExecutorStart(QueryDesc *fc_queryDesc, int fc_eflags)
{
	EState	   *fc_estate;
	MemoryContext fc_oldcontext;

	/* 安全检查：queryDesc 不能已经开始 */
	Assert(fc_queryDesc != NULL);
	Assert(fc_queryDesc->estate == NULL);

	/*
	 * 如果事务是只读的，我们需要检查是否计划对
	 * 非临时表进行任何写入。EXPLAIN 被视为只读。
	 *
	 * 不允许在并行模式下写入。支持 UPDATE 和 DELETE
	 * 将需要 (a) 将组合 CID 哈希存储在共享内存中，而不是
	 * 仅在并行开始时进行一次同步，以及 (b) heap_update()
	 * 对 xmax 的互斥依赖的替代方案。
	 * INSERT 可能没有这样的麻烦，但为了简化检查我们禁止
	 * 它。
	 *
	 * 我们在 CommandCounterIncrement 和其他地方有较低级别的防御
	 * 防止在并行模式下执行不安全的操作，但这提供了一个
	 * 更友好的错误消息。
	 */
	if ((XactReadOnly || IsInParallelMode()) &&
		!(fc_eflags & EXEC_FLAG_EXPLAIN_ONLY))
		fc_ExecCheckXactReadOnly(fc_queryDesc->plannedstmt);

	/*
	 * 构建 EState，切换到用于启动的每查询内存上下文。
	 */
	fc_estate = CreateExecutorState();
	fc_queryDesc->estate = fc_estate;

	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	/*
	 * 从 queryDesc 填充外部参数（如果有）；并为内部参数
	 * 分配工作空间
	 */
	fc_estate->es_param_list_info = fc_queryDesc->params;

	if (fc_queryDesc->plannedstmt->paramExecTypes != NIL)
	{
		int			fc_nParamExec;

		fc_nParamExec = list_length(fc_queryDesc->plannedstmt->paramExecTypes);
		fc_estate->es_param_exec_vals = (ParamExecData *)
			palloc0(fc_nParamExec * sizeof(ParamExecData));
	}

	/* 现在要求所有调用者提供 sourceText */
	Assert(fc_queryDesc->sourceText != NULL);
	fc_estate->es_sourceText = fc_queryDesc->sourceText;

	/*
	 * 从 queryDesc 填充查询环境（如果有）。
	 */
	fc_estate->es_queryEnv = fc_queryDesc->queryEnv;

	/*
	 * 如果是非只读查询，将命令 ID 设置为标记输出元组
	 */
	switch (fc_queryDesc->operation)
	{
		case CMD_SELECT:

			/*
			 * SELECT FOR [KEY] UPDATE/SHARE 和修改 CTE 需要标记
			 * 元组
			 */
			if (fc_queryDesc->plannedstmt->rowMarks != NIL ||
				fc_queryDesc->plannedstmt->hasModifyingCTE)
				fc_estate->es_output_cid = GetCurrentCommandId(true);

			/*
			 * 不带修改 CTE 的 SELECT 不可能排队触发器，
			 * 所以强制跳过触发器模式。这只是一个边际效率
			 * 小技巧，因为 AfterTriggerBeginQuery/AfterTriggerEndQuery
			 * 并不是那么昂贵，但我们也可以这样做。
			 */
			if (!fc_queryDesc->plannedstmt->hasModifyingCTE)
				fc_eflags |= EXEC_FLAG_SKIP_TRIGGERS;
			break;

		case CMD_INSERT:
		case CMD_DELETE:
		case CMD_UPDATE:
		case CMD_MERGE:
			fc_estate->es_output_cid = GetCurrentCommandId(true);
			break;

		default:
			elog(ERROR, "unrecognized operation code: %d",
				 (int) fc_queryDesc->operation);
			break;
	}

	/*
	 * 将其他重要信息复制到 EState
	 */
	fc_estate->es_snapshot = RegisterSnapshot(fc_queryDesc->snapshot);
	fc_estate->es_crosscheck_snapshot = RegisterSnapshot(fc_queryDesc->crosscheck_snapshot);
	fc_estate->es_top_eflags = fc_eflags;
	fc_estate->es_instrument = fc_queryDesc->instrument_options;
	fc_estate->es_jit_flags = fc_queryDesc->plannedstmt->jitFlags;

	/*
	 * 设置 AFTER 触发器语句上下文，除非被告知不这样做，或
	 * 除非处于仅 EXPLAIN 模式（在此模式下不会调用
	 * ExecutorFinish）。
	 */
	if (!(fc_eflags & (EXEC_FLAG_SKIP_TRIGGERS | EXEC_FLAG_EXPLAIN_ONLY)))
		AfterTriggerBeginQuery();

	/*
	 * 初始化计划状态树
	 */
	fc_InitPlan(fc_queryDesc, fc_eflags);

	MemoryContextSwitchTo(fc_oldcontext);
}

/* ----------------------------------------------------------------
 *		ExecutorRun
 *
 *		这是执行器模块的主要例程。它接受来自流量管理器的
 *		查询描述符并执行查询计划。
 *
 *		ExecutorStart 必须已经被调用。
 *
 *		如果方向是 NoMovementScanDirection，则不执行任何操作，
 *		除了启动/关闭目标。否则，
 *		我们在指定方向上检索最多 'count' 行。
 *
 *		注意：count = 0 被解释为没有门户限制，即运行到
 *		完成。此外，注意 count 限制仅适用于
 *		检索的元组，而不适用于例如通过 ModifyTable 计划节点
 *		插入/更新/删除的那些元组。
 *
 *		execute_once 被忽略，仅存在以避免在稳定分支
 *		中造成 API 的破坏。
 *
 *		没有返回值，但输出元组（如果有）将被发送到
 *		QueryDesc 中指定的目标接收器；并且在顶层处理的
 *		元组数量可以在 estate->es_processed 中找到。
 *
 *		我们提供了一个函数钩子变量，允许可加载插件
 *		在调用 ExecutorRun 时获得控制权。这样的插件通常会
 *		调用 standard_ExecutorRun()。
 *
 * ----------------------------------------------------------------
 */
void ExecutorRun(QueryDesc *fc_queryDesc,
			ScanDirection fc_direction, uint64 fc_count,
			bool fc_execute_once)
{
	if (ExecutorRun_hook)
		(*ExecutorRun_hook) (fc_queryDesc, fc_direction, fc_count, fc_execute_once);
	else
		standard_ExecutorRun(fc_queryDesc, fc_direction, fc_count, fc_execute_once);
}

void standard_ExecutorRun(QueryDesc *fc_queryDesc,
					 ScanDirection fc_direction, uint64 fc_count, bool fc_execute_once)
{
	EState	   *fc_estate;
	CmdType		fc_operation;
	DestReceiver *fc_dest;
	bool		fc_sendTuples;
	MemoryContext fc_oldcontext;

	/* 运行检查 */
	Assert(fc_queryDesc != NULL);

	fc_estate = fc_queryDesc->estate;

	Assert(fc_estate != NULL);
	Assert(!(fc_estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY));

	/*
	 * 切换到每个查询的内存上下文
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	/* 允许对执行器总体运行时间进行检测 */
	if (fc_queryDesc->totaltime)
		InstrStartNode(fc_queryDesc->totaltime);

	/* 从查询描述符和查询特征中提取信息。 */
	fc_operation = fc_queryDesc->operation;
	fc_dest = fc_queryDesc->dest;

	/* 启动元组接收器，如果我们将生成元组 */
	fc_estate->es_processed = 0;

	fc_sendTuples = (fc_operation == CMD_SELECT ||
				  fc_queryDesc->plannedstmt->hasReturning);

	if (fc_sendTuples)
		fc_dest->rStartup(fc_dest, fc_operation, fc_queryDesc->tupDesc);

	/* 运行计划 */
	if (!ScanDirectionIsNoMovement(fc_direction))
		fc_ExecutePlan(fc_queryDesc,
					fc_operation,
					fc_sendTuples,
					fc_count,
					fc_direction,
					fc_dest);

	/* 关闭元组接收器，如果我们已经启动它 */
/* ----------------------------------------------------------------
 * 执行器结束
 *
 * 此例程必须在最后一次执行器运行调用后调用。
 * 它执行清理工作，例如触发 AFTER 触发器。它与执行器结束分开，因为 EXPLAIN ANALYZE 需要
 * 将这些操作包含在总运行时间中。
 *
 * 我们提供了一个功能钩子变量，允许可加载插件在调用执行器结束时获得控制。此插件通常会
 * 调用 standard_ExecutorFinish()。
 *
 * ----------------------------------------------------------------
 */
	if (fc_sendTuples)
		fc_dest->rShutdown(fc_dest);

	if (fc_queryDesc->totaltime)
		InstrStopNode(fc_queryDesc->totaltime, fc_estate->es_processed);

	MemoryContextSwitchTo(fc_oldcontext);
}

/* 这应该在每个执行器实例上运行一次且仅运行一次 */
void ExecutorFinish(QueryDesc *fc_queryDesc)
{
	if (ExecutorFinish_hook)
		(*ExecutorFinish_hook) (fc_queryDesc);
	else
		standard_ExecutorFinish(fc_queryDesc);
}

void standard_ExecutorFinish(QueryDesc *fc_queryDesc)
{
	EState	   *fc_estate;
	MemoryContext fc_oldcontext;

	/* 运行检查 */
	Assert(fc_queryDesc != NULL);

	fc_estate = fc_queryDesc->estate;

	Assert(fc_estate != NULL);
	Assert(!(fc_estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY));

	/* 切换到每个查询的内存上下文 */
	Assert(!fc_estate->es_finished);

	/* 运行 ModifyTable 节点以完成 */
	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	/* 允许对执行器总体运行时间进行检测 */
	if (fc_queryDesc->totaltime)
		InstrStartNode(fc_queryDesc->totaltime);

	/* 执行排队的 AFTER 触发器，除非被告知不这样做 */
/* ----------------------------------------------------------------
 * 执行器结束
 *
 * 此例程必须在任何查询计划执行结束时调用
 *
 * 我们提供了一个功能钩子变量，允许可加载插件在调用执行器结束时获得控制。此插件通常会
 * 调用 standard_ExecutorEnd()。
 *
 * ----------------------------------------------------------------
 */
	fc_ExecPostprocessPlan(fc_estate);

	/* 检查执行器结束是否被调用，除非在仅 EXPLAIN 模式下。这个
 * 断言是必需的，因为执行器结束是从 9.1 开始的新功能，调用者
 * 可能会忘记调用它。
 */
	if (!(fc_estate->es_top_eflags & EXEC_FLAG_SKIP_TRIGGERS))
		AfterTriggerEndQuery(fc_estate);

	if (fc_queryDesc->totaltime)
		InstrStopNode(fc_queryDesc->totaltime, 0);

	MemoryContextSwitchTo(fc_oldcontext);

	fc_estate->es_finished = true;
}

/* 切换到每个查询的内存上下文以运行 ExecEndPlan */
void ExecutorEnd(QueryDesc *fc_queryDesc)
{
	if (ExecutorEnd_hook)
		(*ExecutorEnd_hook) (fc_queryDesc);
	else
		standard_ExecutorEnd(fc_queryDesc);
}

void standard_ExecutorEnd(QueryDesc *fc_queryDesc)
{
	EState	   *fc_estate;
	MemoryContext fc_oldcontext;

	/* 运行检查 */
	Assert(fc_queryDesc != NULL);

	fc_estate = fc_queryDesc->estate;

	Assert(fc_estate != NULL);

	/* 处理我们的快照 */
	Assert(fc_estate->es_finished ||
		   (fc_estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY));

	/* 必须在销毁上下文之前切换出上下文 */
	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	fc_ExecEndPlan(fc_queryDesc->planstate, fc_estate);

	/* 释放 EState 和每个查询内存上下文。这应该释放
 * 执行器分配的所有内容。
 */
	UnregisterSnapshot(fc_estate->es_snapshot);
	UnregisterSnapshot(fc_estate->es_crosscheck_snapshot);

	/* 重置不再指向任何内容的 queryDesc 字段 */
/* ----------------------------------------------------------------
 * 执行器回放
 *
 * 此例程可以在打开的 queryDesc 上调用，以将其
 * 回放到开始。
 * ----------------------------------------------------------------
 */
	MemoryContextSwitchTo(fc_oldcontext);

	/* 重新扫描正在更新的查询可能没有意义 */
	FreeExecutorState(fc_estate);

	/* 重新扫描计划 */
/* 执行检查范围表权限
 * 检查范围表中列出的所有关系的访问权限。
 *
 * 如果权限足够，则返回 true。否则，如果 ereport_on_violation 为 true，则抛出适当的
 * 错误，否则返回 false。
 *
 * 请注意，这并不解决行级安全策略（即：RLS）。如果
 * 行将因该权限检查而返回给用户，则还需要咨询 RLS（和 check_enable_rls()）。
 *
 * 请参见 rewrite/rowsecurity.c。
 */
	fc_queryDesc->tupDesc = NULL;
	fc_queryDesc->estate = NULL;
	fc_queryDesc->planstate = NULL;
	fc_queryDesc->totaltime = NULL;
}

/* 执行检查 RTE 权限
 * 检查单个 RTE 的访问权限。
 */
void ExecutorRewind(QueryDesc *fc_queryDesc)
{
	EState	   *fc_estate;
	MemoryContext fc_oldcontext;

	/* 运行检查 */
	Assert(fc_queryDesc != NULL);

	fc_estate = fc_queryDesc->estate;

	Assert(fc_estate != NULL);

	/* 仅需要在此检查普通关系 RTE。函数 RTE
 * 在准备执行函数时检查。连接、子查询
 * 和特殊 RTE 无需检查。
 */
	Assert(fc_queryDesc->operation == CMD_SELECT);

	/*
	 * 切换到每个查询的内存上下文
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	/* 如果 requiredPerms 为空，则不进行任何工作。 */
	ExecReScan(fc_queryDesc->planstate);

	MemoryContextSwitchTo(fc_oldcontext);
}


/* 要检查的用户 ID：当前用户，除非我们有 setuid 指示。
 * 
 * 注意：GetUserId() 目前足够快速，每个 RTE 单独调用它也没有害处。
 * 如果这不再成立，我们可以在 ExecCheckRTPerms 中调用一次，然后从那里传递用户 ID。
 * 但目前不需要额外的混乱。
 */
bool ExecCheckRTPerms(List *fc_rangeTable, bool fc_ereport_on_violation)
{
	ListCell   *fc_l;
	bool		fc_result = true;

	foreach(fc_l, fc_rangeTable)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_l);

		fc_result = fc_ExecCheckRTEPerms(fc_rte);
		if (!fc_result)
		{
			Assert(fc_rte->rtekind == RTE_RELATION);
			if (fc_ereport_on_violation)
				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(fc_rte->relid)),
							   get_rel_name(fc_rte->relid));
			return false;
		}
	}

	if (ExecutorCheckPerms_hook)
		fc_result = (*ExecutorCheckPerms_hook) (fc_rangeTable,
											 fc_ereport_on_violation);
	return fc_result;
}

/* 我们必须拥有 *所有* 所需的权限位，但其中一些位可以通过列级而不是关系级权限来满足。
 * 首先，删除通过关系权限满足的任何位。
 */
static bool fc_ExecCheckRTEPerms(RangeTblEntry *fc_rte)
{
	AclMode		fc_requiredPerms;
	AclMode		fc_relPerms;
	AclMode		fc_remainingPerms;
	Oid			fc_relOid;
	Oid			fc_userid;

	/* 如果我们缺少的任何权限仅存在于关系权限中，
 * 我们可以立即失败。
 */
	if (fc_rte->rtekind != RTE_RELATION)
		return true;

	/* 检查我们是否在列级拥有所需的特权。
 * 
 * 注意：失败仅报告表级错误；如果我们拥有一些但没有
 * 所有列特权，报告列级错误会更好。
 */
	fc_requiredPerms = fc_rte->requiredPerms;
	if (fc_requiredPerms == 0)
		return true;

	fc_relOid = fc_rte->relid;

	/* 当查询没有明确引用任何列时（例如，SELECT COUNT(*) FROM table），如果
 * 我们在关系的任何列上具有 SELECT 权限，则允许查询，符合 SQL 规范。
 */
	fc_userid = fc_rte->checkAsUser ? fc_rte->checkAsUser : GetUserId();

	/* 位 #s 由 FirstLowInvalidHeapAttributeNumber 偏移 */
	fc_relPerms = pg_class_aclmask(fc_relOid, fc_userid, fc_requiredPerms, ACLMASK_ALL);
	fc_remainingPerms = fc_requiredPerms & ~fc_relPerms;
	if (fc_remainingPerms != 0)
	{
		int			fc_col = -1;

		/*
		 * 如果我们缺少仅作为关系权限存在的任何权限，
		 * 我们可以直接失败。
		 */
		if (fc_remainingPerms & ~(ACL_SELECT | ACL_INSERT | ACL_UPDATE))
			return false;

		/*
		 * 检查我们是否具有所需的列级权限。
		 *
		 * 注意：失败仅报告表级错误；如果我们拥有一些但不是所有的
		 * 列权限，报告列级错误会更好。
		 */
		if (fc_remainingPerms & ACL_SELECT)
		{
			/*
			 * 当查询没有明确引用任何列时（例如，SELECT COUNT(*) FROM table），
			 * 如果我们具有关系的任何列的 SELECT 权限，则允许该查询，
			 * 这是根据 SQL 标准的规定。
			 */
			if (bms_is_empty(fc_rte->selectedCols))
			{
				if (pg_attribute_aclcheck_all(fc_relOid, fc_userid, ACL_SELECT,
											  ACLMASK_ANY) != ACLCHECK_OK)
					return false;
			}

			while ((fc_col = bms_next_member(fc_rte->selectedCols, fc_col)) >= 0)
			{
				/* 位元 #s 是否由 FirstLowInvalidHeapAttributeNumber 偏移 */
				AttrNumber	fc_attno = fc_col + FirstLowInvalidHeapAttributeNumber;

				if (fc_attno == InvalidAttrNumber)
				{
					/* 整行引用，必须对所有列有权限 */
					if (pg_attribute_aclcheck_all(fc_relOid, fc_userid, ACL_SELECT,
												  ACLMASK_ALL) != ACLCHECK_OK)
						return false;
				}
				else
				{
					if (pg_attribute_aclcheck(fc_relOid, fc_attno, fc_userid,
											  ACL_SELECT) != ACLCHECK_OK)
						return false;
				}
			}
		}

		/*
		 * 对于 mod 列，基本相同，适用于 INSERT 和 UPDATE 权限
		 * 由 remainingPerms 指定。
		 */
		if (fc_remainingPerms & ACL_INSERT && !fc_ExecCheckRTEPermsModified(fc_relOid,
																	  fc_userid,
																	  fc_rte->insertedCols,
																	  ACL_INSERT))
			return false;

		if (fc_remainingPerms & ACL_UPDATE && !fc_ExecCheckRTEPermsModified(fc_relOid,
																	  fc_userid,
																	  fc_rte->updatedCols,
																	  ACL_UPDATE))
			return false;
	}
	return true;
}

/*
 * ExecCheckRTEPermsModified
 *		检查单个 RTE 的 INSERT 或 UPDATE 访问权限（这些
 *		处理是统一的）。
 */
static bool fc_ExecCheckRTEPermsModified(Oid fc_relOid, Oid fc_userid, Bitmapset *fc_modifiedCols,
						  AclMode fc_requiredPerms)
{
	int			fc_col = -1;

	/*
	 * 当查询没有明确更新任何列时，如果我们对该关系的任何列有权限，
	 * 则允许查询。这是为了处理 SELECT FOR UPDATE 以及可能的 UPDATE 边缘情况。
	 */
	if (bms_is_empty(fc_modifiedCols))
	{
		if (pg_attribute_aclcheck_all(fc_relOid, fc_userid, fc_requiredPerms,
									  ACLMASK_ANY) != ACLCHECK_OK)
			return false;
	}

	while ((fc_col = bms_next_member(fc_modifiedCols, fc_col)) >= 0)
	{
		/* 位元 #s 是否由 FirstLowInvalidHeapAttributeNumber 偏移 */
		AttrNumber	fc_attno = fc_col + FirstLowInvalidHeapAttributeNumber;

		if (fc_attno == InvalidAttrNumber)
		{
			/* 整行引用在这里不能发生 */
			elog(ERROR, "whole-row update is not implemented");
		}
		else
		{
			if (pg_attribute_aclcheck(fc_relOid, fc_attno, fc_userid,
									  fc_requiredPerms) != ACLCHECK_OK)
				return false;
		}
	}
	return true;
}

/*
 * 检查查询不隐含对非临时表的写入；
 * 除非我们处于并行模式，在这种情况下甚至不允许对临时表进行写入。
 *
 * 注意：在热备份中，这将需要拒绝对临时表的写入
 * 就像我们在并行模式中做的那样；但 HS 备份本身不能创建
 * 任何临时表，因此无需检查。
 */
static void fc_ExecCheckXactReadOnly(PlannedStmt *fc_plannedstmt)
{
	ListCell   *fc_l;

	/*
	 * 如果在并行模式下请求表（临时或非临时）的写入权限，则失败，
	 * 否则对于任何非临时表失败。
	 */
	foreach(fc_l, fc_plannedstmt->rtable)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_l);

		if (fc_rte->rtekind != RTE_RELATION)
			continue;

		if ((fc_rte->requiredPerms & (~ACL_SELECT)) == 0)
			continue;

		if (isTempNamespace(get_rel_namespace(fc_rte->relid)))
			continue;

		PreventCommandIfReadOnly(CreateCommandName((Node *) fc_plannedstmt));
	}

	if (fc_plannedstmt->commandType != CMD_SELECT || fc_plannedstmt->hasModifyingCTE)
		PreventCommandIfParallelMode(CreateCommandName((Node *) fc_plannedstmt));
}


/* ----------------------------------------------------------------
 *		InitPlan
 *
 *		初始化查询计划：打开文件，分配存储
 *		并启动规则管理器
 * ----------------------------------------------------------------
 */
static void fc_InitPlan(QueryDesc *fc_queryDesc, int fc_eflags)
{
	CmdType		fc_operation = fc_queryDesc->operation;
	PlannedStmt *fc_plannedstmt = fc_queryDesc->plannedstmt;
	Plan	   *fc_plan = fc_plannedstmt->planTree;
	List	   *fc_rangeTable = fc_plannedstmt->rtable;
	EState	   *fc_estate = fc_queryDesc->estate;
	PlanState  *fc_planstate;
	TupleDesc	fc_tupType;
	ListCell   *fc_l;
	int			fc_i;

	/*
	 * 执行权限检查
	 */
	ExecCheckRTPerms(fc_rangeTable, true);

	/*
	 * 初始化节点的执行状态
	 */
	ExecInitRangeTable(fc_estate, fc_rangeTable);

	fc_estate->es_plannedstmt = fc_plannedstmt;

	/*
	 * 接下来，如果有 PlanRowMark(s)，构建 ExecRowMark 数组。
	 */
	if (fc_plannedstmt->rowMarks)
	{
		fc_estate->es_rowmarks = (ExecRowMark **)
			palloc0(fc_estate->es_range_table_size * sizeof(ExecRowMark *));
		foreach(fc_l, fc_plannedstmt->rowMarks)
		{
			PlanRowMark *fc_rc = (PlanRowMark *) lfirst(fc_l);
			Oid			fc_relid;
			Relation	fc_relation;
			ExecRowMark *fc_erm;

			/* 忽略“父”行标记；在运行时它们是无关的 */
			if (fc_rc->isParent)
				continue;

			/* 获取关系的 OID（如果是子查询将产生 InvalidOid） */
			fc_relid = exec_rt_fetch(fc_rc->rti, fc_estate)->relid;

			/* 打开关系，如果我们需要访问它以获取此标记类型 */
			switch (fc_rc->markType)
			{
				case ROW_MARK_EXCLUSIVE:
				case ROW_MARK_NOKEYEXCLUSIVE:
				case ROW_MARK_SHARE:
				case ROW_MARK_KEYSHARE:
				case ROW_MARK_REFERENCE:
					fc_relation = ExecGetRangeTableRelation(fc_estate, fc_rc->rti);
					break;
				case ROW_MARK_COPY:
					/* 不需要物理表访问 */
					fc_relation = NULL;
					break;
				default:
					elog(ERROR, "unrecognized markType: %d", fc_rc->markType);
					fc_relation = NULL;	/* 保持编译器安静 */
					break;
			}

			/* 检查关系是否是标记的合法目标 */
			if (fc_relation)
				fc_CheckValidRowMarkRel(fc_relation, fc_rc->markType);

			fc_erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
			fc_erm->relation = fc_relation;
			fc_erm->relid = fc_relid;
			fc_erm->rti = fc_rc->rti;
			fc_erm->prti = fc_rc->prti;
			fc_erm->rowmarkId = fc_rc->rowmarkId;
			fc_erm->markType = fc_rc->markType;
			fc_erm->strength = fc_rc->strength;
			fc_erm->waitPolicy = fc_rc->waitPolicy;
			fc_erm->ermActive = false;
			ItemPointerSetInvalid(&(fc_erm->curCtid));
			fc_erm->ermExtra = NULL;

			Assert(fc_erm->rti > 0 && fc_erm->rti <= fc_estate->es_range_table_size &&
				   fc_estate->es_rowmarks[fc_erm->rti - 1] == NULL);

			fc_estate->es_rowmarks[fc_erm->rti - 1] = fc_erm;
		}
	}

	/*
	 * 初始化执行器的元组表为空。
	 */
	fc_estate->es_tupleTable = NIL;

	/* 表示该 EState 不用于 EPQ */
	fc_estate->es_epq_active = NULL;

	/*
	 * 初始化每个子计划的私有状态信息。我们必须在主查询树上运行 ExecInitNode 之前执行此操作，
	 * 因为 ExecInitSubPlan 期望能够找到这些条目。
	 */
	Assert(fc_estate->es_subplanstates == NIL);
	fc_i = 1;						/* 子计划索引从 1 开始计数 */
	foreach(fc_l, fc_plannedstmt->subplans)
	{
		Plan	   *fc_subplan = (Plan *) lfirst(fc_l);
		PlanState  *fc_subplanstate;
		int			fc_sp_eflags;

		/*
		 * 子计划永远不需要执行 BACKWARD 扫描或 MARK/RESTORE。
		 * 如果它是无参数子计划（非 initplan），我们建议它
		 * 准备有效地处理 REWIND；否则没有必要。
		 */
		fc_sp_eflags = fc_eflags
			& (EXEC_FLAG_EXPLAIN_ONLY | EXEC_FLAG_WITH_NO_DATA);
		if (bms_is_member(fc_i, fc_plannedstmt->rewindPlanIDs))
			fc_sp_eflags |= EXEC_FLAG_REWIND;

		fc_subplanstate = ExecInitNode(fc_subplan, fc_estate, fc_sp_eflags);

		fc_estate->es_subplanstates = lappend(fc_estate->es_subplanstates,
										   fc_subplanstate);

		fc_i++;
	}

	/*
	 * 初始化查询树中所有节点的私有状态信息。它打开文件，分配存储，
	 * 并使我们准备好开始处理元组。
	 */
	fc_planstate = ExecInitNode(fc_plan, fc_estate, fc_eflags);

	/*
	 * 获取描述要返回的元组类型的元组描述符。
	 */
	fc_tupType = ExecGetResultType(fc_planstate);

	/*
	 * 如果需要，初始化垃圾过滤器。如果
	 * 最高级 tlist 中有任何垃圾属性，则 SELECT 查询需要过滤器。
	 */
	if (fc_operation == CMD_SELECT)
	{
		bool		fc_junk_filter_needed = false;
		ListCell   *fc_tlist;

		foreach(fc_tlist, fc_plan->targetlist)
		{
			TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tlist);

			if (fc_tle->resjunk)
			{
				fc_junk_filter_needed = true;
				break;
			}
		}

		if (fc_junk_filter_needed)
		{
			JunkFilter *fc_j;
			TupleTableSlot *fc_slot;

			fc_slot = ExecInitExtraTupleSlot(fc_estate, NULL, &TTSOpsVirtual);
			fc_j = ExecInitJunkFilter(fc_planstate->plan->targetlist,
								   fc_slot);
			fc_estate->es_junkFilter = fc_j;

			/* 想要返回清理后的元组类型 */
			fc_tupType = fc_j->jf_cleanTupType;
		}
	}

	fc_queryDesc->tupDesc = fc_tupType;
	fc_queryDesc->planstate = fc_planstate;
}

/*
 * 检查建议的结果关系是否是该操作的合法目标
 *
 * 通常解析器和/或规划器应该已经注意到任何此类错误，
 * 但让我们确保一下。
 *
 * 注意：在更改此函数时，您可能还需要查看
 * CheckValidRowMarkRel。
 */
void CheckValidResultRel(ResultRelInfo *fc_resultRelInfo, CmdType fc_operation)
{
	Relation	fc_resultRel = fc_resultRelInfo->ri_RelationDesc;
	TriggerDesc *fc_trigDesc = fc_resultRel->trigdesc;
	FdwRoutine *fc_fdwroutine;

	/* 期望从 InitResultRelInfo() 获取一个完整的 ResultRelInfo。 */
	Assert(fc_resultRelInfo->ri_needLockTagTuple ==
		   IsInplaceUpdateRelation(fc_resultRel));

	switch (fc_resultRel->rd_rel->relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			CheckCmdReplicaIdentity(fc_resultRel, fc_operation);
			break;
		case RELKIND_SEQUENCE:
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot change sequence \"%s\"",
							RelationGetRelationName(fc_resultRel))));
			break;
		case RELKIND_TOASTVALUE:
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot change TOAST relation \"%s\"",
							RelationGetRelationName(fc_resultRel))));
			break;
		case RELKIND_VIEW:

			/*
			 * 只有在有合适的 INSTEAD OF 触发器时才可以。消息
			 * 应与 rewriteHandler.c 的 rewriteTargetView 和
			 * RewriteQuery 相匹配，只是因为我们没有
			 * 相关信息（并且鉴于我们真的不应该
			 * 到达此处，因此没有必要特别努力去获取）。
			 */
			switch (fc_operation)
			{
				case CMD_INSERT:
					if (!fc_trigDesc || !fc_trigDesc->trig_insert_instead_row)
						ereport(ERROR,
								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
								 errmsg("cannot insert into view \"%s\"",
										RelationGetRelationName(fc_resultRel)),
								 errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.")));
					break;
				case CMD_UPDATE:
					if (!fc_trigDesc || !fc_trigDesc->trig_update_instead_row)
						ereport(ERROR,
								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
								 errmsg("cannot update view \"%s\"",
										RelationGetRelationName(fc_resultRel)),
								 errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.")));
					break;
				case CMD_DELETE:
					if (!fc_trigDesc || !fc_trigDesc->trig_delete_instead_row)
						ereport(ERROR,
								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
								 errmsg("cannot delete from view \"%s\"",
										RelationGetRelationName(fc_resultRel)),
								 errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.")));
					break;
				default:
					elog(ERROR, "unrecognized CmdType: %d", (int) fc_operation);
					break;
			}
			break;
		case RELKIND_MATVIEW:
			if (!MatViewIncrementalMaintenanceIsEnabled())
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot change materialized view \"%s\"",
								RelationGetRelationName(fc_resultRel))));
			break;
		case RELKIND_FOREIGN_TABLE:
			/* 只有在 FDW 支持时才可以 */
			fc_fdwroutine = fc_resultRelInfo->ri_FdwRoutine;
			switch (fc_operation)
			{
				case CMD_INSERT:
					if (fc_fdwroutine->ExecForeignInsert == NULL)
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("cannot insert into foreign table \"%s\"",
										RelationGetRelationName(fc_resultRel))));
					if (fc_fdwroutine->IsForeignRelUpdatable != NULL &&
						(fc_fdwroutine->IsForeignRelUpdatable(fc_resultRel) & (1 << CMD_INSERT)) == 0)
						ereport(ERROR,
								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
								 errmsg("foreign table \"%s\" does not allow inserts",
										RelationGetRelationName(fc_resultRel))));
					break;
				case CMD_UPDATE:
					if (fc_fdwroutine->ExecForeignUpdate == NULL)
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("cannot update foreign table \"%s\"",
										RelationGetRelationName(fc_resultRel))));
					if (fc_fdwroutine->IsForeignRelUpdatable != NULL &&
						(fc_fdwroutine->IsForeignRelUpdatable(fc_resultRel) & (1 << CMD_UPDATE)) == 0)
						ereport(ERROR,
								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
								 errmsg("foreign table \"%s\" does not allow updates",
										RelationGetRelationName(fc_resultRel))));
					break;
				case CMD_DELETE:
					if (fc_fdwroutine->ExecForeignDelete == NULL)
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("cannot delete from foreign table \"%s\"",
										RelationGetRelationName(fc_resultRel))));
					if (fc_fdwroutine->IsForeignRelUpdatable != NULL &&
						(fc_fdwroutine->IsForeignRelUpdatable(fc_resultRel) & (1 << CMD_DELETE)) == 0)
						ereport(ERROR,
								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
								 errmsg("foreign table \"%s\" does not allow deletes",
										RelationGetRelationName(fc_resultRel))));
					break;
				default:
					elog(ERROR, "unrecognized CmdType: %d", (int) fc_operation);
					break;
			}
			break;
		default:
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot change relation \"%s\"",
							RelationGetRelationName(fc_resultRel))));
			break;
	}
}

/*
 * 检查提议的行标记目标关系是否是合法目标
 *
 * 在大多数情况下，解析器和/或计划器应该已经注意到这一点，但
 * 它们并未覆盖所有情况。
 */
static void fc_CheckValidRowMarkRel(Relation fc_rel, RowMarkType fc_markType)
{
	FdwRoutine *fc_fdwroutine;

	switch (fc_rel->rd_rel->relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			/* 好的 */
			break;
		case RELKIND_SEQUENCE:
			/* 必须禁止这一点，因为我们不清理序列 */
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot lock rows in sequence \"%s\"",
							RelationGetRelationName(fc_rel))));
			break;
		case RELKIND_TOASTVALUE:
			/* 我们可以允许这一点，但似乎没有好的理由 */
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot lock rows in TOAST relation \"%s\"",
							RelationGetRelationName(fc_rel))));
			break;
		case RELKIND_VIEW:
			/* 不应该到达这里；计划者应该扩展视图 */
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot lock rows in view \"%s\"",
							RelationGetRelationName(fc_rel))));
			break;
		case RELKIND_MATVIEW:
			/* 允许引用一个物化视图，但不允许实际的锁定条款 */
			if (fc_markType != ROW_MARK_REFERENCE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot lock rows in materialized view \"%s\"",
								RelationGetRelationName(fc_rel))));
			break;
		case RELKIND_FOREIGN_TABLE:
			/* 只有在 FDW 支持时才可以 */
			fc_fdwroutine = GetFdwRoutineForRelation(fc_rel, false);
			if (fc_fdwroutine->RefetchForeignRow == NULL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot lock rows in foreign table \"%s\"",
								RelationGetRelationName(fc_rel))));
			break;
		default:
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot lock rows in relation \"%s\"",
							RelationGetRelationName(fc_rel))));
			break;
	}
}

/*
 * 为一个结果关系初始化 ResultRelInfo 数据
 *
 * 注意：在 Postgres 9.1 之前，这个函数包含了 relkind 检查，
 * 现在转移到了 CheckValidResultRel 中，并且如果合适的话也进行了 ExecOpenIndices。
 * 确保调用者涵盖这些需求。
 */
void InitResultRelInfo(ResultRelInfo *fc_resultRelInfo,
				  Relation fc_resultRelationDesc,
				  Index fc_resultRelationIndex,
				  ResultRelInfo *fc_partition_root_rri,
				  int fc_instrument_options)
{
	MemSet(fc_resultRelInfo, 0, sizeof(ResultRelInfo));
	fc_resultRelInfo->type = T_ResultRelInfo;
	fc_resultRelInfo->ri_RangeTableIndex = fc_resultRelationIndex;
	fc_resultRelInfo->ri_RelationDesc = fc_resultRelationDesc;
	fc_resultRelInfo->ri_NumIndices = 0;
	fc_resultRelInfo->ri_IndexRelationDescs = NULL;
	fc_resultRelInfo->ri_IndexRelationInfo = NULL;
	fc_resultRelInfo->ri_needLockTagTuple =
		IsInplaceUpdateRelation(fc_resultRelationDesc);
	/* 进行复制，以免依赖于 relcache 信息不变... */
	fc_resultRelInfo->ri_TrigDesc = CopyTriggerDesc(fc_resultRelationDesc->trigdesc);
	if (fc_resultRelInfo->ri_TrigDesc)
	{
		int			fc_n = fc_resultRelInfo->ri_TrigDesc->numtriggers;

		fc_resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
			palloc0(fc_n * sizeof(FmgrInfo));
		fc_resultRelInfo->ri_TrigWhenExprs = (ExprState **)
			palloc0(fc_n * sizeof(ExprState *));
		if (fc_instrument_options)
			fc_resultRelInfo->ri_TrigInstrument = InstrAlloc(fc_n, fc_instrument_options, false);
	}
	else
	{
		fc_resultRelInfo->ri_TrigFunctions = NULL;
		fc_resultRelInfo->ri_TrigWhenExprs = NULL;
		fc_resultRelInfo->ri_TrigInstrument = NULL;
	}
	if (fc_resultRelationDesc->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
		fc_resultRelInfo->ri_FdwRoutine = GetFdwRoutineForRelation(fc_resultRelationDesc, true);
	else
		fc_resultRelInfo->ri_FdwRoutine = NULL;

	/* 如果需要，以下字段稍后会被设置 */
	fc_resultRelInfo->ri_RowIdAttNo = 0;
	fc_resultRelInfo->ri_projectNew = NULL;
	fc_resultRelInfo->ri_newTupleSlot = NULL;
	fc_resultRelInfo->ri_oldTupleSlot = NULL;
	fc_resultRelInfo->ri_projectNewInfoValid = false;
	fc_resultRelInfo->ri_FdwState = NULL;
	fc_resultRelInfo->ri_usesFdwDirectModify = false;
	fc_resultRelInfo->ri_ConstraintExprs = NULL;
	fc_resultRelInfo->ri_GeneratedExprs = NULL;
	fc_resultRelInfo->ri_projectReturning = NULL;
	fc_resultRelInfo->ri_onConflictArbiterIndexes = NIL;
	fc_resultRelInfo->ri_onConflict = NULL;
	fc_resultRelInfo->ri_ReturningSlot = NULL;
	fc_resultRelInfo->ri_TrigOldSlot = NULL;
	fc_resultRelInfo->ri_TrigNewSlot = NULL;
	fc_resultRelInfo->ri_matchedMergeAction = NIL;
	fc_resultRelInfo->ri_notMatchedMergeAction = NIL;

	/*
	 * 只有 ExecInitPartitionInfo() 和 ExecInitPartitionDispatchInfo() 会传递
	 * 非 NULL 的 partition_root_rri。对于那些作为初始查询一部分的子关系，而不是
	 * 通过元组路由动态添加的，这个字段在 ExecInitModifyTable() 中填充。
	 */
	fc_resultRelInfo->ri_RootResultRelInfo = fc_partition_root_rri;
	fc_resultRelInfo->ri_RootToPartitionMap = NULL;	/* 由
													 * ExecInitRoutingInfo 设置 */
	fc_resultRelInfo->ri_PartitionTupleSlot = NULL;	/* 同上 */
	fc_resultRelInfo->ri_ChildToRootMap = NULL;
	fc_resultRelInfo->ri_ChildToRootMapValid = false;
	fc_resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}

/*
 * ExecGetTriggerResultRel
 *		获取触发器目标关系的 ResultRelInfo。
 *
 * 大多数情况下，触发器在查询的一个结果关系上被触发，因此我们可以直接返回 es_result_relations 数组的一个成员，或 es_tuple_routing_result_relations 列表中的一项（如果有的话）。(注意：在自连接情形下，可能会有多个成员具有相同的 OID；如果是这样，我们选择哪一个都无所谓。)
 *
 * 然而，有时需要在其他关系上触发触发器；这主要发生在 RI 更新触发器在其他关系上排队额外触发器时，这些将会在外部查询的上下文中处理。为了效率起见，我们也希望为这些触发器有一个 ResultRelInfo；这可以避免重复重新打开关系。(它还提供了一种方法，使 EXPLAIN ANALYZE 可以报告这些触发器的运行时间。)因此，我们根据需要创建额外的 ResultRelInfo，并将它们保存在 es_trig_target_relations 中。
 */
ResultRelInfo * ExecGetTriggerResultRel(EState *fc_estate, Oid fc_relid,
						ResultRelInfo *fc_rootRelInfo)
{
	ResultRelInfo *fc_rInfo;
	ListCell   *fc_l;
	Relation	fc_rel;
	MemoryContext fc_oldcontext;

	/* 在查询结果关系中搜索 */
	foreach(fc_l, fc_estate->es_opened_result_relations)
	{
		fc_rInfo = lfirst(fc_l);
		if (RelationGetRelid(fc_rInfo->ri_RelationDesc) == fc_relid)
			return fc_rInfo;
	}

	/*
	 * 搜索在元组路由期间创建的结果关系（如果有的话）。
	 */
	foreach(fc_l, fc_estate->es_tuple_routing_result_relations)
	{
		fc_rInfo = (ResultRelInfo *) lfirst(fc_l);
		if (RelationGetRelid(fc_rInfo->ri_RelationDesc) == fc_relid)
			return fc_rInfo;
	}

	/* 不，不是，但也许我们已经为它制作了额外的 ResultRelInfo */
	foreach(fc_l, fc_estate->es_trig_target_relations)
	{
		fc_rInfo = (ResultRelInfo *) lfirst(fc_l);
		if (RelationGetRelid(fc_rInfo->ri_RelationDesc) == fc_relid)
			return fc_rInfo;
	}
	/* 不，因此我们需要一个新的 */

	/*
	 * 打开目标关系的 relcache 条目。我们假设后端在触发事件被排队时仍保持适当的锁定，因此我们在这里不需要再次加锁。此外，我们也无需重新检查 relkind，因此不需要检查 CheckValidResultRel。
	 */
	fc_rel = table_open(fc_relid, NoLock);

	/*
	 * 在正确的上下文中创建新条目。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);
	fc_rInfo = makeNode(ResultRelInfo);
	InitResultRelInfo(fc_rInfo,
					  fc_rel,
					  0,		/* 虚拟的范围表索引 */
					  fc_rootRelInfo,
					  fc_estate->es_instrument);
	fc_estate->es_trig_target_relations =
		lappend(fc_estate->es_trig_target_relations, fc_rInfo);
	MemoryContextSwitchTo(fc_oldcontext);

	/*
	 * 目前，我们不需要 ResultRelInfos 中的任何索引信息，仅用于触发器，因此不需要调用 ExecOpenIndices。
	 */

	return fc_rInfo;
}

/*
 * 返回给定叶子分区结果关系的祖先关系，直到并包括查询的根目标关系。
 *
 * 这些关系的工作方式与 ExecGetTriggerResultRel 打开的关系类似，除了我们需要将它们保存在一个单独的列表中。
 *
 * 这些关系通过 ExecCloseResultRelations 关闭。
 */
List * ExecGetAncestorResultRels(EState *fc_estate, ResultRelInfo *fc_resultRelInfo)
{
	ResultRelInfo *fc_rootRelInfo = fc_resultRelInfo->ri_RootResultRelInfo;
	Relation	fc_partRel = fc_resultRelInfo->ri_RelationDesc;
	Oid			fc_rootRelOid;

	if (!fc_partRel->rd_rel->relispartition)
		elog(ERROR, "cannot find ancestors of a non-partition result relation");
	Assert(fc_rootRelInfo != NULL);
	fc_rootRelOid = RelationGetRelid(fc_rootRelInfo->ri_RelationDesc);
	if (fc_resultRelInfo->ri_ancestorResultRels == NIL)
	{
		ListCell   *fc_lc;
		List	   *fc_oids = get_partition_ancestors(RelationGetRelid(fc_partRel));
		List	   *fc_ancResultRels = NIL;

		foreach(fc_lc, fc_oids)
		{
			Oid			fc_ancOid = lfirst_oid(fc_lc);
			Relation	fc_ancRel;
			ResultRelInfo *fc_rInfo;

			/*
			 * 忽略根祖先，并改用 ri_RootResultRelInfo
			 * （如下）来处理。我们在找到查询中提到的表时停止向上攀登
			 * 层级结构。
			 */
			if (fc_ancOid == fc_rootRelOid)
				break;

			/*
			 * 一直到根目标关系的所有祖先必须已经被计划者或 AcquireExecutorLocks() 锁定。
			 */
			fc_ancRel = table_open(fc_ancOid, NoLock);
			fc_rInfo = makeNode(ResultRelInfo);

			/* 虚拟的范围表索引 */
			InitResultRelInfo(fc_rInfo, fc_ancRel, 0, NULL,
							  fc_estate->es_instrument);
			fc_ancResultRels = lappend(fc_ancResultRels, fc_rInfo);
		}
		fc_ancResultRels = lappend(fc_ancResultRels, fc_rootRelInfo);
		fc_resultRelInfo->ri_ancestorResultRels = fc_ancResultRels;
	}

	/* 我们必须找到了一些祖先 */
	Assert(fc_resultRelInfo->ri_ancestorResultRels != NIL);

	return fc_resultRelInfo->ri_ancestorResultRels;
}

/* ----------------------------------------------------------------
 *		ExecPostprocessPlan
 *
 *		在关闭之前给计划节点一个最终执行的机会
 * ----------------------------------------------------------------
 */
static void fc_ExecPostprocessPlan(EState *fc_estate)
{
	ListCell   *fc_lc;

	/*
	 * 确保节点向前运行。
	 */
	fc_estate->es_direction = ForwardScanDirection;

	/*
	 * 运行任何辅助的 ModifyTable 节点以完成，在主查询未从它们中获取所有行的情况下。(我们这样做是为了确保这些节点有可预测的结果。)
	 */
	foreach(fc_lc, fc_estate->es_auxmodifytables)
	{
		PlanState  *fc_ps = (PlanState *) lfirst(fc_lc);

		for (;;)
		{
			TupleTableSlot *fc_slot;

			/* 每次重置每个输出元组的 exprcontext */
			ResetPerTupleExprContext(fc_estate);

			fc_slot = ExecProcNode(fc_ps);

			if (TupIsNull(fc_slot))
				break;
		}
	}
}

/* ----------------------------------------------------------------
 *		ExecEndPlan
 *
 *		清理查询计划 -- 关闭文件并释放存储
 *
 * 注意：我们在这段代码中不再太担心释放存储；FreeExecutorState 应该保证释放所有需要释放的内存。我们担心的是关闭关系和丢弃缓冲区锁定。因此，例如，元组表必须被清空或丢弃以确保释放锁定。
 * ----------------------------------------------------------------
 */
static void fc_ExecEndPlan(PlanState *fc_planstate, EState *fc_estate)
{
	ListCell   *fc_l;

	
/*
	 * 关闭特定于节点类型的查询处理
	 */
	ExecEndNode(fc_planstate);

	/*
	 * 对于子计划也是如此
	 */
	foreach(fc_l, fc_estate->es_subplanstates)
	{
		PlanState  *fc_subplanstate = (PlanState *) lfirst(fc_l);

		ExecEndNode(fc_subplanstate);
	}

	/*
	 * 销毁执行器的元组表。实际上，我们只关心释放缓冲区针和元组描述引用计数；没有必要释放 TupleTableSlots，因为包含的内存上下文反正要消失了。
	 */
	ExecResetTupleTable(fc_estate->es_tupleTable, false);

	/*
	 * 关闭任何已为范围表条目或结果关系打开的关系。
	 */
	ExecCloseResultRelations(fc_estate);
	ExecCloseRangeTableRelations(fc_estate);
}

/*
 * 关闭任何已为 ResultRelInfos 打开的关系。
 */
void ExecCloseResultRelations(EState *fc_estate)
{
	ListCell   *fc_l;

	/*
	 * 如果有的话，关闭结果关系的索引。（关系本身在 ExecCloseRangeTableRelations() 中关闭）
	 *
	 * 此外，关闭每个结果关系的 ri_ancestorResultRels 中可能存在的 stub RT。
	 */
	foreach(fc_l, fc_estate->es_opened_result_relations)
	{
		ResultRelInfo *fc_resultRelInfo = lfirst(fc_l);
		ListCell   *fc_lc;

		ExecCloseIndices(fc_resultRelInfo);
		foreach(fc_lc, fc_resultRelInfo->ri_ancestorResultRels)
		{
			ResultRelInfo *fc_rInfo = lfirst(fc_lc);

			/*
			 * RTI > 0 的祖先（应该只有根祖先）由 ExecCloseRangeTableRelations 关闭。
			 */
			if (fc_rInfo->ri_RangeTableIndex > 0)
				continue;

			table_close(fc_rInfo->ri_RelationDesc, NoLock);
		}
	}

	/* 关闭任何已由 ExecGetTriggerResultRel() 打开的关系。 */
	foreach(fc_l, fc_estate->es_trig_target_relations)
	{
		ResultRelInfo *fc_resultRelInfo = (ResultRelInfo *) lfirst(fc_l);

		/*
		 * 断言这是一个“虚假” ResultRelInfo，见上文。否则，我们可能会对
		 * ExecGetRangeTableRelation 打开的关系发出重复关闭。
		 */
		Assert(fc_resultRelInfo->ri_RangeTableIndex == 0);

		/*
		 * 由于 ExecGetTriggerResultRel 不会为这些关系调用 ExecOpenIndices，
		 * 我们也不需要调用 ExecCloseIndices。
		 */
		Assert(fc_resultRelInfo->ri_NumIndices == 0);

		table_close(fc_resultRelInfo->ri_RelationDesc, NoLock);
	}
}

/*
 * 关闭所有由 ExecGetRangeTableRelation() 打开的关系。
 *
 * 我们不会释放可能对这些关系的任何锁定。
 */
void ExecCloseRangeTableRelations(EState *fc_estate)
{
	int			fc_i;

	for (fc_i = 0; fc_i < fc_estate->es_range_table_size; fc_i++)
	{
		if (fc_estate->es_relations[fc_i])
			table_close(fc_estate->es_relations[fc_i], NoLock);
	}
}

/* ----------------------------------------------------------------
 *		ExecutePlan
 *
 *		处理查询计划，直到我们检索到 'numberTuples' 个元组，
 *		按指定方向移动。
 *
 *		如果 numberTuples 为 0，则运行到完成
 * ----------------------------------------------------------------
 */
static void fc_ExecutePlan(QueryDesc *fc_queryDesc,
			CmdType fc_operation,
			bool fc_sendTuples,
			uint64 fc_numberTuples,
			ScanDirection fc_direction,
			DestReceiver *fc_dest)
{
	EState	   *fc_estate = fc_queryDesc->estate;
	PlanState  *fc_planstate = fc_queryDesc->planstate;
	bool		fc_use_parallel_mode;
	TupleTableSlot *fc_slot;
	uint64		fc_current_tuple_count;

	/*
	 * 初始化局部变量
	 */
	fc_current_tuple_count = 0;

	/*
	 * 设置方向。
	 */
	fc_estate->es_direction = fc_direction;

	/*
	 * 如果适用，设置并行模式。
	 *
	 * 并行模式仅支持计划的完整执行。如果我们已经部分执行它，或
	 * 如果调用方要求我们提前退出，我们必须强制计划在没有并行处理的情况下运行。
	 */
	if (fc_queryDesc->already_executed || fc_numberTuples != 0)
		fc_use_parallel_mode = false;
	else
		fc_use_parallel_mode = fc_queryDesc->plannedstmt->parallelModeNeeded;
	fc_queryDesc->already_executed = true;

	fc_estate->es_use_parallel_mode = fc_use_parallel_mode;
	if (fc_use_parallel_mode)
		EnterParallelMode();

	/*
	 * 在处理完计划中的适当数量的元组之前循环。
	 */
	for (;;)
	{
		/* 重置每个输出元组的 exprcontext */
		ResetPerTupleExprContext(fc_estate);

		/*
		 * 执行计划并获取一个元组
		 */
		fc_slot = ExecProcNode(fc_planstate);

		/*
		 * 如果元组为 null，则我们假设没有更多的内容可处理，因此我们
		 * 只需结束循环...
		 */
		if (TupIsNull(fc_slot))
			break;

		/*
		 * 如果我们有一个垃圾过滤器，则投影一个去掉垃圾的新元组。
		 *
		 * 将这个新的“干净”元组存储在垃圾过滤器的 resultSlot 中。
		 * （以前，我们将其存储在“脏”元组上，这是错误的，因为那个元组槽有
		 * 错误的描述符。）
		 */
		if (fc_estate->es_junkFilter != NULL)
			fc_slot = ExecFilterJunk(fc_estate->es_junkFilter, fc_slot);

		/*
		 * 如果我们应该将元组发送到某处，就这样做。（在实践中，这
		 * 在这一点上可能总是如此。）
		 */
		if (fc_sendTuples)
		{
			/*
			 * 如果我们无法发送元组，我们假设目标已关闭，不能再发送更多
			 * 元组。如果是这样，结束循环。
			 */
			if (!fc_dest->receiveSlot(fc_slot, fc_dest))
				break;
		}

		/*
		 * 统计已处理的元组，如果这是一个 SELECT。（对于其他操作类型，
		 * ModifyTable 计划节点必须统计相应的事件。）
		 */
		if (fc_operation == CMD_SELECT)
			(fc_estate->es_processed)++;

		/*
		 * 检查我们的元组计数.. 如果我们已处理适当数量，则退出，否则
		 * 循环并处理更多元组。零个 numberTuples 表示没有限制。
		 */
		fc_current_tuple_count++;
		if (fc_numberTuples && fc_numberTuples == fc_current_tuple_count)
			break;
	}

	/*
	 * 如果我们知道不需要回退，现在可以释放资源。
	 */
	if (!(fc_estate->es_top_eflags & EXEC_FLAG_BACKWARD))
		(void) ExecShutdownNode(fc_planstate);

	if (fc_use_parallel_mode)
		ExitParallelMode();
}


/*
 * ExecRelCheck --- 检查元组是否符合结果关系的约束
 *
 * 如果 OK 则返回 NULL，否则返回未通过检查的约束名称
 */
static const char * fc_ExecRelCheck(ResultRelInfo *fc_resultRelInfo,
			 TupleTableSlot *fc_slot, EState *fc_estate)
{
	Relation	fc_rel = fc_resultRelInfo->ri_RelationDesc;
	int			fc_ncheck = fc_rel->rd_att->constr->num_check;
	ConstrCheck *fc_check = fc_rel->rd_att->constr->check;
	ExprContext *fc_econtext;
	MemoryContext fc_oldContext;
	int			fc_i;

	/*
	 * CheckConstraintFetch 让这个仅以警告通过，但现在我们应该失败，
	 * 而不是可能未能执行重要约束。
	 */
	if (fc_ncheck != fc_rel->rd_rel->relchecks)
		elog(ERROR, "%d pg_constraint record(s) missing for relation \"%s\"",
			 fc_rel->rd_rel->relchecks - fc_ncheck, RelationGetRelationName(fc_rel));

	/*
	 * 如果是第一次遍历此结果关系，构建关系约束表达式的表达式
	 * 节点树。将它们保存在每个查询的内存上下文中，以便它们在整个查询中生存。
	 */
	if (fc_resultRelInfo->ri_ConstraintExprs == NULL)
	{
		fc_oldContext = MemoryContextSwitchTo(fc_estate->es_query_cxt);
		fc_resultRelInfo->ri_ConstraintExprs =
			(ExprState **) palloc(fc_ncheck * sizeof(ExprState *));
		for (fc_i = 0; fc_i < fc_ncheck; fc_i++)
		{
			Expr	   *fc_checkconstr;

			fc_checkconstr = stringToNode(fc_check[fc_i].ccbin);
			fc_resultRelInfo->ri_ConstraintExprs[fc_i] =
				ExecPrepareExpr(fc_checkconstr, fc_estate);
		}
		MemoryContextSwitchTo(fc_oldContext);
	}

	/*
	 * 我们将使用 EState 的每元组上下文来评估约束表达式（如果尚未存在，则创建它）。
	 */
	fc_econtext = GetPerTupleExprContext(fc_estate);

	/* 安排econtext的扫描元组为待测试的元组 */
	fc_econtext->ecxt_scantuple = fc_slot;

	/* 并评估约束 */
	for (fc_i = 0; fc_i < fc_ncheck; fc_i++)
	{
		ExprState  *fc_checkconstr = fc_resultRelInfo->ri_ConstraintExprs[fc_i];

		/*
		 * 注意：SQL 指定约束表达式的 NULL 结果不能被视为失败。
		 * 因此，使用 ExecCheck 而不是 ExecQual。
		 */
		if (!ExecCheck(fc_checkconstr, fc_econtext))
			return fc_check[fc_i].ccname;
	}

	/* NULL 结果表示没有错误 */
	return NULL;
}

/*
 * ExecPartitionCheck --- 检查元组是否符合分区约束。
 *
 * 如果符合分区约束，则返回 true。如果约束失败且请求我们发出错误，
 * 则这样做并且不返回；否则返回 false。
 */
bool ExecPartitionCheck(ResultRelInfo *fc_resultRelInfo, TupleTableSlot *fc_slot,
				   EState *fc_estate, bool fc_emitError)
{
	ExprContext *fc_econtext;
	bool		fc_success;

	/*
	 * 如果是第一次处理，为分区检查表达式构建表达式状态树。
	 * （在分区检查表达式为空的边缘情况下，即只有默认分区而没有其他内容时，
	 * 我们会被愚弄，每次都执行这段代码。但是在这种情况下，成本非常低，因此我们不担心。）
	 */
	if (fc_resultRelInfo->ri_PartitionCheckExpr == NULL)
	{
		/*
		 * 确保 qual tree 和准备好的表达式在查询生命周期上下文中。
		 */
		MemoryContext fc_oldcxt = MemoryContextSwitchTo(fc_estate->es_query_cxt);
		List	   *fc_qual = RelationGetPartitionQual(fc_resultRelInfo->ri_RelationDesc);

		fc_resultRelInfo->ri_PartitionCheckExpr = ExecPrepareCheck(fc_qual, fc_estate);
		MemoryContextSwitchTo(fc_oldcxt);
	}

	/*
	 * 我们将使用 EState 的每元组上下文来评估约束表达式（如果尚未存在，则创建它）。
	 */
	fc_econtext = GetPerTupleExprContext(fc_estate);

	/* 安排econtext的扫描元组为待测试的元组 */
	fc_econtext->ecxt_scantuple = fc_slot;

	/*
	 * 在目录约束的情况下，我们将 NULL 结果视为成功，而不是失败。
	 */
	fc_success = ExecCheck(fc_resultRelInfo->ri_PartitionCheckExpr, fc_econtext);

	/* 如果请求发出错误，则在失败时不实际返回 */
	if (!fc_success && fc_emitError)
		ExecPartitionCheckEmitError(fc_resultRelInfo, fc_slot, fc_estate);

	return fc_success;
}

/*
 * ExecPartitionCheckEmitError - 在分区约束检查失败后形成并发出错误消息。
 */
void ExecPartitionCheckEmitError(ResultRelInfo *fc_resultRelInfo,
							TupleTableSlot *fc_slot,
							EState *fc_estate)
{
	Oid			fc_root_relid;
	TupleDesc	fc_tupdesc;
	char	   *fc_val_desc;
	Bitmapset  *fc_modifiedCols;

	/*
	 * 如果元组已经被路由，它已经被转换为分区的行类型，
	 * 这可能与根表的行类型不同。我们必须将其转换回根表的行类型，
	 * 以便错误消息中的 val_desc 与输入元组匹配。
	 */
	if (fc_resultRelInfo->ri_RootResultRelInfo)
	{
		ResultRelInfo *fc_rootrel = fc_resultRelInfo->ri_RootResultRelInfo;
		TupleDesc	fc_old_tupdesc;
		AttrMap    *fc_map;

		fc_root_relid = RelationGetRelid(fc_rootrel->ri_RelationDesc);
		fc_tupdesc = RelationGetDescr(fc_rootrel->ri_RelationDesc);

		fc_old_tupdesc = RelationGetDescr(fc_resultRelInfo->ri_RelationDesc);
		/* 反向映射 */
		fc_map = build_attrmap_by_name_if_req(fc_old_tupdesc, fc_tupdesc);

		/*
		 * 特定于分区的槽的 tupdesc 不能更改，因此分配一个新的。
		 */
		if (fc_map != NULL)
			fc_slot = execute_attr_map_slot(fc_map, fc_slot,
										 MakeTupleTableSlot(fc_tupdesc, &TTSOpsVirtual));
		fc_modifiedCols = bms_union(ExecGetInsertedCols(fc_rootrel, fc_estate),
								 ExecGetUpdatedCols(fc_rootrel, fc_estate));
	}
	else
	{
		fc_root_relid = RelationGetRelid(fc_resultRelInfo->ri_RelationDesc);
		fc_tupdesc = RelationGetDescr(fc_resultRelInfo->ri_RelationDesc);
		fc_modifiedCols = bms_union(ExecGetInsertedCols(fc_resultRelInfo, fc_estate),
								 ExecGetUpdatedCols(fc_resultRelInfo, fc_estate));
	}

	fc_val_desc = fc_ExecBuildSlotValueDescription(fc_root_relid,
											 fc_slot,
											 fc_tupdesc,
											 fc_modifiedCols,
											 64);
	ereport(ERROR,
			(errcode(ERRCODE_CHECK_VIOLATION),
			 errmsg("new row for relation \"%s\" violates partition constraint",
					RelationGetRelationName(fc_resultRelInfo->ri_RelationDesc)),
			 fc_val_desc ? errdetail("Failing row contains %s.", fc_val_desc) : 0,
			 errtable(fc_resultRelInfo->ri_RelationDesc)));
}

/*
 * ExecConstraints - 检查 'slot' 中元组的约束
 *
 * 这会检查传统的 NOT NULL 和检查约束。
 *
 * 不会检查分区约束。
 *
 * 注意：'slot' 包含要检查约束的元组，该元组可能已经在元组路由后从原始输入元组转换而来。
 * 'resultRelInfo' 是最终结果关系，在元组路由之后。
 */
void ExecConstraints(ResultRelInfo *fc_resultRelInfo,
				TupleTableSlot *fc_slot, EState *fc_estate)
{
	Relation	fc_rel = fc_resultRelInfo->ri_RelationDesc;
	TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);
	TupleConstr *fc_constr = fc_tupdesc->constr;
	Bitmapset  *fc_modifiedCols;

	Assert(fc_constr);				/* 我们不应以其他方式被调用 */

	if (fc_constr->has_not_null)
	{
		int			fc_natts = fc_tupdesc->natts;
		int			fc_attrChk;

		for (fc_attrChk = 1; fc_attrChk <= fc_natts; fc_attrChk++)
		{
			Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_attrChk - 1);

			if (fc_att->attnotnull && slot_attisnull(fc_slot, fc_attrChk))
			{
				char	   *fc_val_desc;
				Relation	fc_orig_rel = fc_rel;
				TupleDesc	fc_orig_tupdesc = RelationGetDescr(fc_rel);

				/*
				 * 如果元组已经被路由，它已经被转换为
				 * 分区的行类型，这可能与根表的行类型不同。
				 * 我们必须将其转换回根表的行类型，以便错误消息中显示的 val_desc 与输入元组匹配。
				 */
				if (fc_resultRelInfo->ri_RootResultRelInfo)
				{
					ResultRelInfo *fc_rootrel = fc_resultRelInfo->ri_RootResultRelInfo;
					AttrMap    *fc_map;

					fc_tupdesc = RelationGetDescr(fc_rootrel->ri_RelationDesc);
					/* 反向映射 */
					fc_map = build_attrmap_by_name_if_req(fc_orig_tupdesc,
													   fc_tupdesc);

					/*
					 * 特定于分区的槽的 tupdesc 不能更改，因此
					 * 分配一个新的。
					 */
					if (fc_map != NULL)
						fc_slot = execute_attr_map_slot(fc_map, fc_slot,
													 MakeTupleTableSlot(fc_tupdesc, &TTSOpsVirtual));
					fc_modifiedCols = bms_union(ExecGetInsertedCols(fc_rootrel, fc_estate),
											 ExecGetUpdatedCols(fc_rootrel, fc_estate));
					fc_rel = fc_rootrel->ri_RelationDesc;
				}
				else
					fc_modifiedCols = bms_union(ExecGetInsertedCols(fc_resultRelInfo, fc_estate),
											 ExecGetUpdatedCols(fc_resultRelInfo, fc_estate));
				fc_val_desc = fc_ExecBuildSlotValueDescription(RelationGetRelid(fc_rel),
														 fc_slot,
														 fc_tupdesc,
														 fc_modifiedCols,
														 64);

				ereport(ERROR,
						(errcode(ERRCODE_NOT_NULL_VIOLATION),
						 errmsg("null value in column \"%s\" of relation \"%s\" violates not-null constraint",
								NameStr(fc_att->attname),
								RelationGetRelationName(fc_orig_rel)),
						 fc_val_desc ? errdetail("Failing row contains %s.", fc_val_desc) : 0,
						 errtablecol(fc_orig_rel, fc_attrChk)));
			}
		}
	}

	if (fc_rel->rd_rel->relchecks > 0)
	{
		const char *fc_failed;

		if ((fc_failed = fc_ExecRelCheck(fc_resultRelInfo, fc_slot, fc_estate)) != NULL)
		{
			char	   *fc_val_desc;
			Relation	fc_orig_rel = fc_rel;

			/* 参见上面的注释。 */
			if (fc_resultRelInfo->ri_RootResultRelInfo)
			{
				ResultRelInfo *fc_rootrel = fc_resultRelInfo->ri_RootResultRelInfo;
				TupleDesc	fc_old_tupdesc = RelationGetDescr(fc_rel);
				AttrMap    *fc_map;

				fc_tupdesc = RelationGetDescr(fc_rootrel->ri_RelationDesc);
				/* 反向映射 */
				fc_map = build_attrmap_by_name_if_req(fc_old_tupdesc,
												   fc_tupdesc);

				/*
				 * 特定于分区的槽的 tupdesc 不能更改，因此
				 * 分配一个新的。
				 */
				if (fc_map != NULL)
					fc_slot = execute_attr_map_slot(fc_map, fc_slot,
												 MakeTupleTableSlot(fc_tupdesc, &TTSOpsVirtual));
				fc_modifiedCols = bms_union(ExecGetInsertedCols(fc_rootrel, fc_estate),
										 ExecGetUpdatedCols(fc_rootrel, fc_estate));
				fc_rel = fc_rootrel->ri_RelationDesc;
			}
			else
				fc_modifiedCols = bms_union(ExecGetInsertedCols(fc_resultRelInfo, fc_estate),
										 ExecGetUpdatedCols(fc_resultRelInfo, fc_estate));
			fc_val_desc = fc_ExecBuildSlotValueDescription(RelationGetRelid(fc_rel),
													 fc_slot,
													 fc_tupdesc,
													 fc_modifiedCols,
													 64);
			ereport(ERROR,
					(errcode(ERRCODE_CHECK_VIOLATION),
					 errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
							RelationGetRelationName(fc_orig_rel), fc_failed),
					 fc_val_desc ? errdetail("Failing row contains %s.", fc_val_desc) : 0,
					 errtableconstraint(fc_orig_rel, fc_failed)));
		}
	}
}

/*
 * ExecWithCheckOptions -- 检查元组是否满足指定类型的任何 WITH CHECK OPTION。
 *
 * 注意，这需要多次调用以确保处理所有类型的
 * WITH CHECK OPTION（包括来自具有设置 WITH CHECK OPTION 的视图和行级安全政策的选项）。
 * 参见 ExecInsert() 和 ExecUpdate()。
 */
void ExecWithCheckOptions(WCOKind fc_kind, ResultRelInfo *fc_resultRelInfo,
					 TupleTableSlot *fc_slot, EState *fc_estate)
{
	Relation	fc_rel = fc_resultRelInfo->ri_RelationDesc;
	TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);
	ExprContext *fc_econtext;
	ListCell   *fc_l1,
			   *fc_l2;

	/*
	 * 我们将使用 EState 的每元组上下文来评估约束表达式（如果尚未存在，则创建它）。
	 */
	fc_econtext = GetPerTupleExprContext(fc_estate);

	/* 安排econtext的扫描元组为待测试的元组 */
	fc_econtext->ecxt_scantuple = fc_slot;

	/* 检查每一个约束 */
	forboth(fc_l1, fc_resultRelInfo->ri_WithCheckOptions,
			fc_l2, fc_resultRelInfo->ri_WithCheckOptionExprs)
	{
		WithCheckOption *fc_wco = (WithCheckOption *) lfirst(fc_l1);
		ExprState  *fc_wcoExpr = (ExprState *) lfirst(fc_l2);

		/*
		 * 跳过任何我们此时不寻求的 WCO。
		 */
		if (fc_wco->kind != fc_kind)
			continue;

		/*
		 * WITH CHECK OPTION 检查旨在确保新元组是可见的
		 * （在视图的情况下）或它通过了
		 * 'with-check' 策略（在行安全的情况下）。如果 qual
		 * 评估为 NULL 或 FALSE，则新元组将不会包含在
		 * 视图中或未通过表的 'with-check' 策略。
		 */
		if (!ExecQual(fc_wcoExpr, fc_econtext))
		{
			char	   *fc_val_desc;
			Bitmapset  *fc_modifiedCols;

			switch (fc_wco->kind)
			{
					/*
					 * 对于来自视图的 WITH CHECK OPTION，
					 * 我们可能能够提供有关行的详细信息，
					 * 这取决于关系的权限（即，如果用户
					 * 直接查看它也可以）。对于 RLS 违规，
					 * 我们不包括数据，因为我们不知道用户
					 * 是否应该能够查看该元组，因为这取决于
					 * USING 策略。
					 */
				case WCO_VIEW_CHECK:
					/* 参见 ExecConstraints() 中的注释。 */
					if (fc_resultRelInfo->ri_RootResultRelInfo)
					{
						ResultRelInfo *fc_rootrel = fc_resultRelInfo->ri_RootResultRelInfo;
						TupleDesc	fc_old_tupdesc = RelationGetDescr(fc_rel);
						AttrMap    *fc_map;

						fc_tupdesc = RelationGetDescr(fc_rootrel->ri_RelationDesc);
						/* 反向映射 */
						fc_map = build_attrmap_by_name_if_req(fc_old_tupdesc,
														   fc_tupdesc);

						/*
						 * 特定于分区的槽的 tupdesc 不能更改，
						 * 因此分配一个新的。
						 */
						if (fc_map != NULL)
							fc_slot = execute_attr_map_slot(fc_map, fc_slot,
														 MakeTupleTableSlot(fc_tupdesc, &TTSOpsVirtual));

						fc_modifiedCols = bms_union(ExecGetInsertedCols(fc_rootrel, fc_estate),
												 ExecGetUpdatedCols(fc_rootrel, fc_estate));
						fc_rel = fc_rootrel->ri_RelationDesc;
					}
					else
						fc_modifiedCols = bms_union(ExecGetInsertedCols(fc_resultRelInfo, fc_estate),
												 ExecGetUpdatedCols(fc_resultRelInfo, fc_estate));
					fc_val_desc = fc_ExecBuildSlotValueDescription(RelationGetRelid(fc_rel),
															 fc_slot,
															 fc_tupdesc,
															 fc_modifiedCols,
															 64);

					ereport(ERROR,
							(errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
							 errmsg("new row violates check option for view \"%s\"",
									fc_wco->relname),
							 fc_val_desc ? errdetail("Failing row contains %s.",
												  fc_val_desc) : 0));
					break;
				case WCO_RLS_INSERT_CHECK:
				case WCO_RLS_UPDATE_CHECK:
					if (fc_wco->polname != NULL)
						ereport(ERROR,
								(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
								 errmsg("new row violates row-level security policy \"%s\" for table \"%s\"",
										fc_wco->polname, fc_wco->relname)));
					else
						ereport(ERROR,
								(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
								 errmsg("new row violates row-level security policy for table \"%s\"",
										fc_wco->relname)));
					break;
				case WCO_RLS_MERGE_UPDATE_CHECK:
				case WCO_RLS_MERGE_DELETE_CHECK:
					if (fc_wco->polname != NULL)
						ereport(ERROR,
								(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
								 errmsg("target row violates row-level security policy \"%s\" (USING expression) for table \"%s\"",
										fc_wco->polname, fc_wco->relname)));
					else
						ereport(ERROR,
								(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
								 errmsg("target row violates row-level security policy (USING expression) for table \"%s\"",
										fc_wco->relname)));
					break;
				case WCO_RLS_CONFLICT_CHECK:
					if (fc_wco->polname != NULL)
						ereport(ERROR,
								(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
								 errmsg("new row violates row-level security policy \"%s\" (USING expression) for table \"%s\"",
										fc_wco->polname, fc_wco->relname)));
					else
						ereport(ERROR,
								(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
								 errmsg("new row violates row-level security policy (USING expression) for table \"%s\"",
										fc_wco->relname)));
					break;
				default:
					elog(ERROR, "unrecognized WCO kind: %u", fc_wco->kind);
					break;
			}
		}
	}
}

/*
 * ExecBuildSlotValueDescription -- 构造一个表示元组的字符串
 *
 * 这有意与 BuildIndexValueDescription 非常相似，
 * 但与该函数不同的是，我们截断长字段值（最多为 maxfieldlen
 * 字节）。在这里似乎是必要的，因为堆字段值可能非常长，而索引条目通常不会那么宽。
 *
 * 此外，与索引条目的情况不同，我们需要准备好忽略
 * 被删除的列。我们曾经使用槽的元组描述符来解码数据，
 * 但槽的描述符不标识删除的列，因此我们现在需要传递
 * 关系的描述符。
 *
 * 注意，像 BuildIndexValueDescription 一样，如果用户没有
 * 权限查看任何相关列，则返回 NULL。
 * 与 BuildIndexValueDescription 不同的是，如果用户有权查看相关列的子集，
 * 则会返回该子集，并带有标识这些列的键。
 */
static char * fc_ExecBuildSlotValueDescription(Oid fc_reloid,
							  TupleTableSlot *fc_slot,
							  TupleDesc fc_tupdesc,
							  Bitmapset *fc_modifiedCols,
							  int fc_maxfieldlen)
{
	StringInfoData fc_buf;
	StringInfoData fc_collist;
	bool		fc_write_comma = false;
	bool		fc_write_comma_collist = false;
	int			fc_i;
	AclResult	fc_aclresult;
	bool		fc_table_perm = false;
	bool		fc_any_perm = false;

	
/*
	 * 检查RLS是否启用并且应该对该关系处于激活状态；如果是，则不返回任何内容。否则，进行正常的权限检查。
	 */
	if (check_enable_rls(fc_reloid, InvalidOid, true) == RLS_ENABLED)
		return NULL;

	initStringInfo(&fc_buf);

	appendStringInfoChar(&fc_buf, '(');

	/*
	 * 检查用户是否有权限查看该行。表级SELECT允许访问所有列。如果用户没有表级SELECT权限，则检查每一列，并包括用户具有SELECT权限的列。此外，我们始终包括用户提供数据的列。
	 */
	fc_aclresult = pg_class_aclcheck(fc_reloid, GetUserId(), ACL_SELECT);
	if (fc_aclresult != ACLCHECK_OK)
	{
		/* 设置列列表的缓冲区 */
		initStringInfo(&fc_collist);
		appendStringInfoChar(&fc_collist, '(');
	}
	else
		fc_table_perm = fc_any_perm = true;

	/* 确保元组已完全解构 */
	slot_getallattrs(fc_slot);

	for (fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
	{
		bool		fc_column_perm = false;
		char	   *fc_val;
		int			fc_vallen;
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);

		/* 忽略已删除的列 */
		if (fc_att->attisdropped)
			continue;

		if (!fc_table_perm)
		{
			/*
			 * 没有表级SELECT，因此需要确保用户在列上具有SELECT权限，或者他们已提供该列的数据。如果没有，则从错误消息中省略此列。
			 */
			fc_aclresult = pg_attribute_aclcheck(fc_reloid, fc_att->attnum,
											  GetUserId(), ACL_SELECT);
			if (bms_is_member(fc_att->attnum - FirstLowInvalidHeapAttributeNumber,
							  fc_modifiedCols) || fc_aclresult == ACLCHECK_OK)
			{
				fc_column_perm = fc_any_perm = true;

				if (fc_write_comma_collist)
					appendStringInfoString(&fc_collist, ", ");
				else
					fc_write_comma_collist = true;

				appendStringInfoString(&fc_collist, NameStr(fc_att->attname));
			}
		}

		if (fc_table_perm || fc_column_perm)
		{
			if (fc_slot->tts_isnull[fc_i])
				fc_val = "null";
			else
			{
				Oid			fc_foutoid;
				bool		fc_typisvarlena;

				getTypeOutputInfo(fc_att->atttypid,
								  &fc_foutoid, &fc_typisvarlena);
				fc_val = OidOutputFunctionCall(fc_foutoid, fc_slot->tts_values[fc_i]);
			}

			if (fc_write_comma)
				appendStringInfoString(&fc_buf, ", ");
			else
				fc_write_comma = true;

			/* 根据需要截断 */
			fc_vallen = strlen(fc_val);
			if (fc_vallen <= fc_maxfieldlen)
				appendBinaryStringInfo(&fc_buf, fc_val, fc_vallen);
			else
			{
				fc_vallen = pg_mbcliplen(fc_val, fc_vallen, fc_maxfieldlen);
				appendBinaryStringInfo(&fc_buf, fc_val, fc_vallen);
				appendStringInfoString(&fc_buf, "...");
			}
		}
	}

	/* 如果返回的列数为零，则返回NULL。 */
	if (!fc_any_perm)
		return NULL;

	appendStringInfoChar(&fc_buf, ')');

	if (!fc_table_perm)
	{
		appendStringInfoString(&fc_collist, ") = ");
		appendBinaryStringInfo(&fc_collist, fc_buf.data, fc_buf.len);

		return fc_collist.data;
	}

	return fc_buf.data;
}


/*
 * ExecUpdateLockMode -- 查找给定 ResultRelInfo 的适当 UPDATE 元组锁模式
 */
LockTupleMode
ExecUpdateLockMode(EState *fc_estate, ResultRelInfo *fc_relinfo)
{
	Bitmapset  *fc_keyCols;
	Bitmapset  *fc_updatedCols;

	/*
	 * 计算要使用的锁模式。如果作为键的列没有被修改，
	 * 则我们可以使用较弱的锁，从而允许更好的并发性。
	 */
	fc_updatedCols = ExecGetAllUpdatedCols(fc_relinfo, fc_estate);
	fc_keyCols = RelationGetIndexAttrBitmap(fc_relinfo->ri_RelationDesc,
										 INDEX_ATTR_BITMAP_KEY);

	if (bms_overlap(fc_keyCols, fc_updatedCols))
		return LockTupleExclusive;

	return LockTupleNoKeyExclusive;
}

/*
 * ExecFindRowMark -- 查找给定 rangetable 索引的 ExecRowMark 结构
 *
 * 如果没有这样的结构，则根据 missing_ok 返回 NULL 或抛出错误
 */
ExecRowMark *
ExecFindRowMark(EState *fc_estate, Index fc_rti, bool fc_missing_ok)
{
	if (fc_rti > 0 && fc_rti <= fc_estate->es_range_table_size &&
		fc_estate->es_rowmarks != NULL)
	{
		ExecRowMark *fc_erm = fc_estate->es_rowmarks[fc_rti - 1];

		if (fc_erm)
			return fc_erm;
	}
	if (!fc_missing_ok)
		elog(ERROR, "failed to find ExecRowMark for rangetable index %u", fc_rti);
	return NULL;
}

/*
 * ExecBuildAuxRowMark -- 创建一个 ExecAuxRowMark 结构
 *
 * 输入是底层的 ExecRowMark 结构和输入计划节点的目标列表
 *（不是 planstate 节点！）。我们需要后者来找出 resjunk 列的列号。
 */
ExecAuxRowMark *
ExecBuildAuxRowMark(ExecRowMark *fc_erm, List *fc_targetlist)
{
	ExecAuxRowMark *fc_aerm = (ExecAuxRowMark *) palloc0(sizeof(ExecAuxRowMark));
	char		fc_resname[32];

	fc_aerm->rowmark = fc_erm;

	/* 查找与此 rowmark 关联的 resjunk 列 */
	if (fc_erm->markType != ROW_MARK_COPY)
	{
		/* 对于除 COPY 外的所有方法需要 ctid */
		snprintf(fc_resname, sizeof(fc_resname), "ctid%u", fc_erm->rowmarkId);
		fc_aerm->ctidAttNo = ExecFindJunkAttributeInTlist(fc_targetlist,
													   fc_resname);
		if (!AttributeNumberIsValid(fc_aerm->ctidAttNo))
			elog(ERROR, "could not find junk %s column", fc_resname);
	}
	else
	{
		/* 如果是 COPY，则需要 wholerow */
		snprintf(fc_resname, sizeof(fc_resname), "wholerow%u", fc_erm->rowmarkId);
		fc_aerm->wholeAttNo = ExecFindJunkAttributeInTlist(fc_targetlist,
														fc_resname);
		if (!AttributeNumberIsValid(fc_aerm->wholeAttNo))
			elog(ERROR, "could not find junk %s column", fc_resname);
	}

	/* 如果是子关系，则需要 tableoid */
	if (fc_erm->rti != fc_erm->prti)
	{
		snprintf(fc_resname, sizeof(fc_resname), "tableoid%u", fc_erm->rowmarkId);
		fc_aerm->toidAttNo = ExecFindJunkAttributeInTlist(fc_targetlist,
													   fc_resname);
		if (!AttributeNumberIsValid(fc_aerm->toidAttNo))
			elog(ERROR, "could not find junk %s column", fc_resname);
	}

	return fc_aerm;
}


/*
 * EvalPlanQual 逻辑 --- 重新检查修改过的元组以确定我们是否希望按照
 * READ COMMITTED 规则处理更新的版本。
 *
 * 有关此工作的某些信息，请参见 backend/executor/README。
 */


/*
 * 检查元组的更新版本，以确定我们是否希望在
 * READ COMMITTED 规则下处理它。
 *
 *	epqstate - EvalPlanQual 重新检查的状态
 *	relation - 包含元组的表
 *	rti - 包含元组的表的 rangetable 索引
 *	inputslot - 要处理的元组 - 这可以是
 *		EvalPlanQualSlot() 的来自该关系的槽，以提高效率。
 *
 * 此测试检查 inputslot 中的元组是否仍然与相关的
 * quals 匹配。为了使该结果有用，通常输入元组必须是
 * 最后一行版本（否则结果没有特别的用处）并且
 * 被锁定（否则结果可能过时）。这通常
 * 通过使用 table_tuple_lock() 和
 * TUPLE_LOCK_FLAG_FIND_LAST_VERSION 标志来实现。
 *
 * 返回包含新候选更新/删除元组的槽，或者
 * 如果我们确定不应处理该行，则返回 NULL。
 */
TupleTableSlot * EvalPlanQual(EPQState *fc_epqstate, Relation fc_relation,
			 Index fc_rti, TupleTableSlot *fc_inputslot)
{
	TupleTableSlot *fc_slot;
	TupleTableSlot *fc_testslot;

	Assert(fc_rti > 0);

	/*
	 * 需要运行重新检查的子查询。初始化或重新初始化 EPQ 状态。
	 */
	EvalPlanQualBegin(fc_epqstate);

	/*
	 * 调用者通常会使用 EvalPlanQualSlot 来存储元组，以避免
	 * 不必要的复制。
	 */
	fc_testslot = EvalPlanQualSlot(fc_epqstate, fc_relation, fc_rti);
	if (fc_testslot != fc_inputslot)
		ExecCopySlot(fc_testslot, fc_inputslot);

	/*
	 * 标记该关系的 EPQ 元组是可用的。（如果有
	 * 超过一个结果关系，则其他关系保持标记为没有
	 * 可用元组。）
	 */
	fc_epqstate->relsubs_done[fc_rti - 1] = false;
	fc_epqstate->epqExtra->relsubs_blocked[fc_rti - 1] = false;

	/*
	 * 执行 EPQ 查询。我们假设它最多返回一个元组。
	 */
	fc_slot = EvalPlanQualNext(fc_epqstate);

	/*
	 * 如果我们得到了一个元组，则强制槽来物化该元组，
	 * 以使其不依赖于 EPQ 查询中的任何本地状态（特别是，
	 * 槽很可能包含对 copyTuple 中可能存在的任何按引用传递的
	 * 数据项的引用）。与下一步一样，这
	 * 是为了防止 EPQ 查询的提前重用。
	 */
	if (!TupIsNull(fc_slot))
		ExecMaterializeSlot(fc_slot);

	/*
	 * 清除测试元组，并标记在此处没有可用的元组。
	 * 这在 EPQ 状态被重新用于检验不同目标关系的元组时是必要的。
	 */
	ExecClearTuple(fc_testslot);
	fc_epqstate->epqExtra->relsubs_blocked[fc_rti - 1] = true;

	return fc_slot;
}

/*
 * EvalPlanQualInit -- 在创建可能需要调用 EPQ 处理的计划状态节点时初始化。
 *
 * 注意：subplan/auxrowmarks 可以是 NULL/NIL，如果稍后将用
 * EvalPlanQualSetPlan 设置它们。
 */
void EvalPlanQualInit(EPQState *fc_epqstate, EState *fc_parentestate,
				 Plan *fc_subplan, List *fc_auxrowmarks, int fc_epqParam)
{
	EvalPlanQualInitExt(fc_epqstate, fc_parentestate,
						fc_subplan, fc_auxrowmarks, fc_epqParam, NIL);
}

/*
 * EvalPlanQualInitExt -- 相同，但允许指定 resultRelations
 *
 * 如果调用者打算使用 EvalPlanQual()，那么 resultRelations 应该是
 * 潜在目标关系的 RT 索引列表，
 * 我们将确保其他列出的关系在 EvalPlanQual() 调用期间不返回任何
 * 元组。否则，resultRelations 应该是 NIL。
 */
void EvalPlanQualInitExt(EPQState *fc_epqstate, EState *fc_parentestate,
					Plan *fc_subplan, List *fc_auxrowmarks,
					int fc_epqParam, List *fc_resultRelations)
{
	Index		fc_rtsize = fc_parentestate->es_range_table_size;

	/* 创建一些额外空间以避免 ABI 断裂 */
	fc_epqstate->epqExtra = (EPQStateExtra *) palloc(sizeof(EPQStateExtra));

	/* 初始化在 EPQState 生命周期内不改变的数据 */
	fc_epqstate->parentestate = fc_parentestate;
	fc_epqstate->epqParam = fc_epqParam;
	fc_epqstate->epqExtra->resultRelations = fc_resultRelations;

	/*
	 * 为每个潜在的 rti 分配引用槽的空间 - 现在这样做
	 * 而不是在 EvalPlanQualBegin() 中，如其他动态
	 * 分配资源那样，以便可以使用 EvalPlanQualSlot()
	 * 来保存可能稍后需要 EPQ 的元组，而无需强制
	 * EvalPlanQualBegin() 的开销。
	 */
	fc_epqstate->epqExtra->tuple_table = NIL;
	fc_epqstate->relsubs_slot = (TupleTableSlot **)
		palloc0(fc_rtsize * sizeof(TupleTableSlot *));

	/* ... 并记住 EvalPlanQualBegin 需要的数据 */
	fc_epqstate->plan = fc_subplan;
	fc_epqstate->arowMarks = fc_auxrowmarks;

	/* ... 并标记 EPQ 状态为不活动 */
	fc_epqstate->origslot = NULL;
	fc_epqstate->recheckestate = NULL;
	fc_epqstate->recheckplanstate = NULL;
	fc_epqstate->relsubs_rowmark = NULL;
	fc_epqstate->relsubs_done = NULL;
	fc_epqstate->epqExtra->relsubs_blocked = NULL;
}

/*
 * EvalPlanQualSetPlan -- 设置或更改 EPQState 的子计划。
 *
 * 我们过去需要这个，以便 ModifyTable 可以处理多个子计划。
 * 现在可以将其重构得不再需要它。
 */
void EvalPlanQualSetPlan(EPQState *fc_epqstate, Plan *fc_subplan, List *fc_auxrowmarks)
{
	/* 如果我们有一个活动的 EPQ 查询，则关闭它 */
	EvalPlanQualEnd(fc_epqstate);
	/* 并设置/更改计划指针 */
	fc_epqstate->plan = fc_subplan;
	
/* 行标记也取决于计划 */
	fc_epqstate->arowMarks = fc_auxrowmarks;
}

/*
 * 返回，并在必要时创建一个用于 EPQ 测试元组的槽位。
 *
 * 请注意，这仅需要调用 EvalPlanQualInit()，
 * EvalPlanQualBegin() 不是必需的。
 */
TupleTableSlot * EvalPlanQualSlot(EPQState *fc_epqstate,
				 Relation fc_relation, Index fc_rti)
{
	TupleTableSlot **fc_slot;

	Assert(fc_relation);
	Assert(fc_rti > 0 && fc_rti <= fc_epqstate->parentestate->es_range_table_size);
	fc_slot = &fc_epqstate->relsubs_slot[fc_rti - 1];

	if (*fc_slot == NULL)
	{
		MemoryContext fc_oldcontext;

		fc_oldcontext = MemoryContextSwitchTo(fc_epqstate->parentestate->es_query_cxt);
		*fc_slot = table_slot_create(fc_relation, &fc_epqstate->epqExtra->tuple_table);
		MemoryContextSwitchTo(fc_oldcontext);
	}

	return *fc_slot;
}

/*
 * 获取一个非锁定关系的当前行值，该关系由 rti 标识，
 * 需要通过 EvalPlanQual 操作进行扫描。 origslot 必须已设置为包含
 * 当前结果行（顶层行），我们需要重新检查。 如果找到替代元组，则返回 true，否则返回 false。
 */
bool EvalPlanQualFetchRowMark(EPQState *fc_epqstate, Index fc_rti, TupleTableSlot *fc_slot)
{
	ExecAuxRowMark *fc_earm = fc_epqstate->relsubs_rowmark[fc_rti - 1];
	ExecRowMark *fc_erm = fc_earm->rowmark;
	Datum		fc_datum;
	bool		fc_isNull;

	Assert(fc_earm != NULL);
	Assert(fc_epqstate->origslot != NULL);

	if (RowMarkRequiresRowShareLock(fc_erm->markType))
		elog(ERROR, "EvalPlanQual doesn't support locking rowmarks");

	/* 如果是子关系，必须检查是否产生了这一行 */
	if (fc_erm->rti != fc_erm->prti)
	{
		Oid			fc_tableoid;

		fc_datum = ExecGetJunkAttribute(fc_epqstate->origslot,
									 fc_earm->toidAttNo,
									 &fc_isNull);
		/* 未锁定的关系可能位于外连接的内部 */
		if (fc_isNull)
			return false;

		fc_tableoid = DatumGetObjectId(fc_datum);

		Assert(OidIsValid(fc_erm->relid));
		if (fc_tableoid != fc_erm->relid)
		{
			/* 这个子项目前处于非活动状态 */
			return false;
		}
	}

	if (fc_erm->markType == ROW_MARK_REFERENCE)
	{
		Assert(fc_erm->relation != NULL);

		/* 获取元组的 ctid */
		fc_datum = ExecGetJunkAttribute(fc_epqstate->origslot,
									 fc_earm->ctidAttNo,
									 &fc_isNull);
		/* 未锁定的关系可能位于外连接的内部 */
		if (fc_isNull)
			return false;

		/* 对外表的获取请求必须传递给它们的 FDW */
		if (fc_erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
		{
			FdwRoutine *fc_fdwroutine;
			bool		fc_updated = false;

			fc_fdwroutine = GetFdwRoutineForRelation(fc_erm->relation, false);
			/* 这应该已经检查过了，但为了安全起见还是检查一下 */
			if (fc_fdwroutine->RefetchForeignRow == NULL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot lock rows in foreign table \"%s\"",
								RelationGetRelationName(fc_erm->relation))));

			fc_fdwroutine->RefetchForeignRow(fc_epqstate->recheckestate,
										  fc_erm,
										  fc_datum,
										  fc_slot,
										  &fc_updated);
			if (TupIsNull(fc_slot))
				elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");

			/*
			 * 理想情况下，我们应该坚持在这里更新 == false，但这假设
			 * FDW 能够准确地跟踪这一点，而它们可能无法做到。
			 * 所以直接忽略这个标志。
			 */
			return true;
		}
		else
		{
			/* 普通表，获取元组 */
			if (!table_tuple_fetch_row_version(fc_erm->relation,
											   (ItemPointer) DatumGetPointer(fc_datum),
											   SnapshotAny, fc_slot))
				elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
			return true;
		}
	}
	else
	{
		Assert(fc_erm->markType == ROW_MARK_COPY);

		/* 为关系获取整个行的 Var */
		fc_datum = ExecGetJunkAttribute(fc_epqstate->origslot,
									 fc_earm->wholeAttNo,
									 &fc_isNull);
		/* 未锁定的关系可能位于外连接的内部 */
		if (fc_isNull)
			return false;

		ExecStoreHeapTupleDatum(fc_datum, fc_slot);
		return true;
	}
}

/*
 * 从 EvalPlanQual 测试中获取下一行（如果有的话）
 *
 * （实际上，应该永远不会有多于一行……）
 */
TupleTableSlot * EvalPlanQualNext(EPQState *fc_epqstate)
{
	MemoryContext fc_oldcontext;
	TupleTableSlot *fc_slot;

	fc_oldcontext = MemoryContextSwitchTo(fc_epqstate->recheckestate->es_query_cxt);
	fc_slot = ExecProcNode(fc_epqstate->recheckplanstate);
	MemoryContextSwitchTo(fc_oldcontext);

	return fc_slot;
}

/*
 * 初始化或重置 EvalPlanQual 状态树
 */
void EvalPlanQualBegin(EPQState *fc_epqstate)
{
	EState	   *fc_parentestate = fc_epqstate->parentestate;
	EState	   *fc_recheckestate = fc_epqstate->recheckestate;

	if (fc_recheckestate == NULL)
	{
		/* 第一次执行，因此创建一个子 EState */
		fc_EvalPlanQualStart(fc_epqstate, fc_epqstate->plan);
	}
	else
	{
		/*
		 * 我们已经有一个合适的子 EPQ 树了，所以只需重置它。
		 */
		Index		fc_rtsize = fc_parentestate->es_range_table_size;
		PlanState  *fc_rcplanstate = fc_epqstate->recheckplanstate;

		/*
		 * 将 relsubs_done[] 标志重置为与 relsubs_blocked[] 相等，以便
		 * EPQ 运行永远不会试图从被阻止的目标关系中获取元组。
		 */
		memcpy(fc_epqstate->relsubs_done, fc_epqstate->epqExtra->relsubs_blocked,
			   fc_rtsize * sizeof(bool));

		/* 重新复制当前父参数的值 */
		if (fc_parentestate->es_plannedstmt->paramExecTypes != NIL)
		{
			int			fc_i;

			/*
			 * 强制评估任何 InitPlan 输出，这些输出可能是
			 * 子计划所需要的，以防它们自 EvalPlanQualStart 以来
			 * 被重置（见其中的注释）。
			 */
			ExecSetParamPlanMulti(fc_rcplanstate->plan->extParam,
								  GetPerTupleExprContext(fc_parentestate));

			fc_i = list_length(fc_parentestate->es_plannedstmt->paramExecTypes);

			while (--fc_i >= 0)
			{
				/* 复制值（如果有的话），但不复制 execPlan 链接 */
				fc_recheckestate->es_param_exec_vals[fc_i].value =
					fc_parentestate->es_param_exec_vals[fc_i].value;
				fc_recheckestate->es_param_exec_vals[fc_i].isnull =
					fc_parentestate->es_param_exec_vals[fc_i].isnull;
			}
		}

		/*
		 * 标记子计划树以需要在所有扫描节点上重新扫描。第一个
		 * ExecProcNode 将负责实际执行重新扫描。
		 */
		fc_rcplanstate->chgParam = bms_add_member(fc_rcplanstate->chgParam,
											   fc_epqstate->epqParam);
	}
}

/*
 * 开始执行 EvalPlanQual 计划树。
 *
 * 这是一个简化版本的 ExecutorStart()：我们从
 * 顶层 estate 复制一些状态，而不是重新初始化它。
 */
static void fc_EvalPlanQualStart(EPQState *fc_epqstate, Plan *fc_planTree)
{
	EState	   *fc_parentestate = fc_epqstate->parentestate;
	Index		fc_rtsize = fc_parentestate->es_range_table_size;
	EState	   *fc_rcestate;
	MemoryContext fc_oldcontext;
	ListCell   *fc_l;

	fc_epqstate->recheckestate = fc_rcestate = CreateExecutorState();

	fc_oldcontext = MemoryContextSwitchTo(fc_rcestate->es_query_cxt);

	/* 表示这是一个用于执行 EPQ 的 EState */
	fc_rcestate->es_epq_active = fc_epqstate;

	/*
	 * 子 EPQ EState 共享父级的不可变状态副本，如
	 * 快照、范围表和外部参数信息。它们需要自己的
	 * 本地状态副本，包括元组表、es_param_exec_vals、
	 * 结果关系信息等。
	 */
	fc_rcestate->es_direction = ForwardScanDirection;
	fc_rcestate->es_snapshot = fc_parentestate->es_snapshot;
	fc_rcestate->es_crosscheck_snapshot = fc_parentestate->es_crosscheck_snapshot;
	fc_rcestate->es_range_table = fc_parentestate->es_range_table;
	fc_rcestate->es_range_table_size = fc_parentestate->es_range_table_size;
	fc_rcestate->es_relations = fc_parentestate->es_relations;
	fc_rcestate->es_queryEnv = fc_parentestate->es_queryEnv;
	fc_rcestate->es_rowmarks = fc_parentestate->es_rowmarks;
	fc_rcestate->es_plannedstmt = fc_parentestate->es_plannedstmt;
	fc_rcestate->es_junkFilter = fc_parentestate->es_junkFilter;
	fc_rcestate->es_output_cid = fc_parentestate->es_output_cid;

	/*
	 * 子计划所需的 ResultRelInfos 在初始化子计划时从头开始初始化。
	 */
	fc_rcestate->es_result_relations = NULL;
	/* es_trig_target_relations 绝不能被复制 */
	fc_rcestate->es_top_eflags = fc_parentestate->es_top_eflags;
	fc_rcestate->es_instrument = fc_parentestate->es_instrument;
	/* es_auxmodifytables 绝不能被复制 */

	/*
	 * 外部参数列表仅从父级共享。内部
	 * 参数工作区必须是本地状态，但我们从父级复制初始值，
	 * 以便访问已从父级计划树其他部分设置的任何参数值。
	 */
	fc_rcestate->es_param_list_info = fc_parentestate->es_param_list_info;
	if (fc_parentestate->es_plannedstmt->paramExecTypes != NIL)
	{
		int			fc_i;

		/*
		 * 强制评估任何 InitPlan 输出，这些输出可能是
		 * 子计划所需要的。（随着复杂性的提高，也许可以将其推迟
		 * 直到子计划实际需要它们，但这似乎不值得费力；
		 * 因为通常 InitPlans 会在到达 EvalPlanQual 之前被评估。）
		 *
		 * 这不会触及子计划树某处的 InitPlans 输出参数，仅触及
		 * 附加到 ModifyTable 节点或其上方的参数，并在子计划中引用。
		 * 不过这没关系，因为规划器只会将这样的
		 * InitPlans 附加到较低级别的 SubqueryScan 节点，并且
		 * EPQ 执行不会深入 SubqueryScan。
		 *
		 * EState 的每个输出元组 econtext 的生命周期足够短
		 * 以满足这一要求，因为它应在有机会再次执行
		 * EvalPlanQual 之前被重置。
		 */
		ExecSetParamPlanMulti(fc_planTree->extParam,
							  GetPerTupleExprContext(fc_parentestate));

		/* 现在创建内部参数工作区... */
		fc_i = list_length(fc_parentestate->es_plannedstmt->paramExecTypes);
		fc_rcestate->es_param_exec_vals = (ParamExecData *)
			palloc0(fc_i * sizeof(ParamExecData));
		/* ...并复制所有值，无论是否真的需要 */
		while (--fc_i >= 0)
		{
			/* 复制值（如果有的话），但不复制 execPlan 链接 */
			fc_rcestate->es_param_exec_vals[fc_i].value =
				fc_parentestate->es_param_exec_vals[fc_i].value;
			fc_rcestate->es_param_exec_vals[fc_i].isnull =
				fc_parentestate->es_param_exec_vals[fc_i].isnull;
		}
	}

	/*
	 * 为每个 SubPlan 初始化私有状态信息。我们必须在运行
	 * ExecInitNode 之前这样做，因为 ExecInitSubPlan 希望能够找到这些条目。
	 * 一些 SubPlans 可能在我们打算运行的计划树部分中没有被使用，
	 * 但由于不容易确定哪些，我们只需初始化它们。
	 */
	Assert(fc_rcestate->es_subplanstates == NIL);
	foreach(fc_l, fc_parentestate->es_plannedstmt->subplans)
	{
		Plan	   *fc_subplan = (Plan *) lfirst(fc_l);
		PlanState  *fc_subplanstate;

		fc_subplanstate = ExecInitNode(fc_subplan, fc_rcestate, 0);
		fc_rcestate->es_subplanstates = lappend(fc_rcestate->es_subplanstates,
											 fc_subplanstate);
	}

	/*
	 * 构建一个 RTI 索引的行标记数组，以便
	 * EvalPlanQualFetchRowMark() 可以高效访问要获取的
	 * 行标记。
	 */
	fc_epqstate->relsubs_rowmark = (ExecAuxRowMark **)
		palloc0(fc_rtsize * sizeof(ExecAuxRowMark *));
	foreach(fc_l, fc_epqstate->arowMarks)
	{
		ExecAuxRowMark *fc_earm = (ExecAuxRowMark *) lfirst(fc_l);

		fc_epqstate->relsubs_rowmark[fc_earm->rowmark->rti - 1] = fc_earm;
	}

	/*
	 * 初始化每个关系的 EPQ 元组状态。结果关系（如果有）会被标记为被阻塞；其他关系则标记为未提取。
	 */
	fc_epqstate->relsubs_done = palloc_array(bool, fc_rtsize);
	fc_epqstate->epqExtra->relsubs_blocked = palloc0_array(bool, fc_rtsize);

	foreach(fc_l, fc_epqstate->epqExtra->resultRelations)
	{
		int			fc_rtindex = lfirst_int(fc_l);

		Assert(fc_rtindex > 0 && fc_rtindex <= fc_rtsize);
		fc_epqstate->epqExtra->relsubs_blocked[fc_rtindex - 1] = true;
	}

	memcpy(fc_epqstate->relsubs_done, fc_epqstate->epqExtra->relsubs_blocked,
		   fc_rtsize * sizeof(bool));

	/*
	 * 初始化我们需要运行的计划树部分中所有节点的私有状态信息。这会打开文件，分配存储，并使我们准备好开始处理元组。
	 */
	fc_epqstate->recheckplanstate = ExecInitNode(fc_planTree, fc_rcestate, 0);

	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * EvalPlanQualEnd -- 在父计划状态节点终止时关闭，或者如果我们完成了当前的 EPQ 子节点。
 *
 * 这是 ExecutorEnd() 的简化版本；基本上我们希望进行大部分正常清理，但*不*关闭结果关系（因为我们只是从外部查询共享它们）。然而，我们确实必须关闭任何打开的结果和触发目标关系，因为这些不是共享的。（可能不应该有后者，但以防万一……）
 */
void EvalPlanQualEnd(EPQState *fc_epqstate)
{
	EState	   *fc_estate = fc_epqstate->recheckestate;
	Index		fc_rtsize;
	MemoryContext fc_oldcontext;
	ListCell   *fc_l;

	fc_rtsize = fc_epqstate->parentestate->es_range_table_size;

	/*
	 * 即使 EPQ 未启动，我们可能仍然有一个元组表，因为我们允许在不调用 EvalPlanQualBegin() 的情况下使用 EvalPlanQualSlot()。
	 */
	if (fc_epqstate->epqExtra->tuple_table != NIL)
	{
		memset(fc_epqstate->relsubs_slot, 0,
			   fc_rtsize * sizeof(TupleTableSlot *));
		ExecResetTupleTable(fc_epqstate->epqExtra->tuple_table, true);
		fc_epqstate->epqExtra->tuple_table = NIL;
	}

	/* EPQ 未启动，没什么更多的工作要做 */
	if (fc_estate == NULL)
		return;

	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	ExecEndNode(fc_epqstate->recheckplanstate);

	foreach(fc_l, fc_estate->es_subplanstates)
	{
		PlanState  *fc_subplanstate = (PlanState *) lfirst(fc_l);

		ExecEndNode(fc_subplanstate);
	}

	/* 丢弃每个 estate 的元组表，某些节点可能已经使用过 */
	ExecResetTupleTable(fc_estate->es_tupleTable, false);

	/* 关闭与此 EState 相关联的任何结果和触发目标关系 */
	ExecCloseResultRelations(fc_estate);

	MemoryContextSwitchTo(fc_oldcontext);

	FreeExecutorState(fc_estate);

	/* 将 EPQState 标记为闲置 */
	fc_epqstate->origslot = NULL;
	fc_epqstate->recheckestate = NULL;
	fc_epqstate->recheckplanstate = NULL;
	fc_epqstate->relsubs_rowmark = NULL;
	fc_epqstate->relsubs_done = NULL;
	fc_epqstate->epqExtra->relsubs_blocked = NULL;
}
